create-tauri-app.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. #!/usr/bin/env node
  2. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  3. // SPDX-License-Identifier: Apache-2.0
  4. // SPDX-License-Identifier: MIT
  5. const parseArgs = require("minimist");
  6. const inquirer = require("inquirer");
  7. const { resolve, join } = require("path");
  8. const {
  9. recipeShortNames,
  10. recipeDescriptiveNames,
  11. recipeByDescriptiveName,
  12. recipeByShortName,
  13. install,
  14. checkPackageManager,
  15. shell,
  16. } = require("../dist/");
  17. /**
  18. * @type {object}
  19. * @property {boolean} h
  20. * @property {boolean} help
  21. * @property {boolean} v
  22. * @property {boolean} version
  23. * @property {string|boolean} f
  24. * @property {string|boolean} force
  25. * @property {boolean} l
  26. * @property {boolean} log
  27. * @property {boolean} d
  28. * @property {boolean} directory
  29. * @property {string} r
  30. * @property {string} recipe
  31. */
  32. const createTauriApp = async (cliArgs) => {
  33. const argv = parseArgs(cliArgs, {
  34. alias: {
  35. h: "help",
  36. v: "version",
  37. f: "force",
  38. l: "log",
  39. m: "manager",
  40. d: "directory",
  41. b: "binary",
  42. t: "tauri-path",
  43. A: "app-name",
  44. W: "window-title",
  45. D: "dist-dir",
  46. P: "dev-path",
  47. r: "recipe",
  48. },
  49. boolean: ["h", "l", "ci"],
  50. });
  51. if (argv.help) {
  52. printUsage();
  53. return 0;
  54. }
  55. if (argv.v) {
  56. console.log(require("../package.json").version);
  57. return false; // do this for node consumers and tests
  58. }
  59. if (argv.ci) {
  60. return runInit(argv);
  61. } else {
  62. return getOptionsInteractive(argv).then((responses) =>
  63. runInit(argv, responses)
  64. );
  65. }
  66. };
  67. function printUsage() {
  68. console.log(`
  69. Description
  70. Starts a new tauri app from a "recipe" or pre-built template.
  71. Usage
  72. $ yarn create tauri-app <app-name> # npm create-tauri-app <app-name>
  73. Options
  74. --help, -h Displays this message
  75. -v, --version Displays the Tauri CLI version
  76. --ci Skip prompts
  77. --force, -f Force init to overwrite [conf|template|all]
  78. --log, -l Logging [boolean]
  79. --manager, -d Set package manager to use [npm|yarn]
  80. --directory, -d Set target directory for init
  81. --binary, -b Optional path to a tauri binary from which to run init
  82. --app-name, -A Name of your Tauri application
  83. --window-title, -W Window title of your Tauri application
  84. --dist-dir, -D Web assets location, relative to <project-dir>/src-tauri
  85. --dev-path, -P Url of your dev server
  86. --recipe, -r Add UI framework recipe. None by default.
  87. Supported recipes: [${recipeShortNames.join("|")}]
  88. `);
  89. }
  90. const getOptionsInteractive = (argv) => {
  91. let defaultAppName = argv.A || "tauri-app";
  92. return inquirer
  93. .prompt([
  94. {
  95. type: "input",
  96. name: "appName",
  97. message: "What is your app name?",
  98. default: defaultAppName,
  99. when: !argv.A,
  100. },
  101. {
  102. type: "input",
  103. name: "tauri.window.title",
  104. message: "What should the window title be?",
  105. default: "Tauri App",
  106. when: () => !argv.W,
  107. },
  108. {
  109. type: "list",
  110. name: "recipeName",
  111. message: "Would you like to add a UI recipe?",
  112. choices: recipeDescriptiveNames,
  113. default: "No recipe",
  114. when: () => !argv.r,
  115. },
  116. ])
  117. .catch((error) => {
  118. if (error.isTtyError) {
  119. // Prompt couldn't be rendered in the current environment
  120. console.log(
  121. "It appears your terminal does not support interactive prompts. Using default values."
  122. );
  123. runInit();
  124. } else {
  125. // Something else went wrong
  126. console.error("An unknown error occurred:", error);
  127. }
  128. });
  129. };
  130. async function runInit(argv, config = {}) {
  131. const {
  132. appName,
  133. recipeName,
  134. tauri: {
  135. window: { title },
  136. },
  137. } = config;
  138. // this little fun snippet pulled from vite determines the package manager the script was run from
  139. const packageManager = /yarn/.test(process.env.npm_execpath) ? "yarn" : "npm";
  140. let recipe;
  141. if (recipeName !== undefined) {
  142. recipe = recipeByDescriptiveName(recipeName);
  143. } else if (argv.r) {
  144. recipe = recipeByShortName(argv.r);
  145. }
  146. let buildConfig = {
  147. distDir: argv.D,
  148. devPath: argv.P,
  149. };
  150. if (recipe !== undefined) {
  151. buildConfig = recipe.configUpdate({ buildConfig, packageManager });
  152. }
  153. const directory = argv.d || process.cwd();
  154. const cfg = {
  155. ...buildConfig,
  156. appName: appName || argv.A,
  157. windowTitle: title || argv.w,
  158. };
  159. // note that our app directory is reliant on the appName and
  160. // generally there are issues if the path has spaces (see Windows)
  161. // future TODO prevent app names with spaces or escape here?
  162. const appDirectory = join(directory, cfg.appName);
  163. // this throws an error if we can't run the package manager they requested
  164. await checkPackageManager({ cwd: directory, packageManager });
  165. if (recipe.preInit) {
  166. console.log("===== running initial command(s) =====");
  167. await recipe.preInit({ cwd: directory, cfg, packageManager });
  168. }
  169. const initArgs = [
  170. ["--app-name", cfg.appName],
  171. ["--window-title", cfg.windowTitle],
  172. ["--dist-dir", cfg.distDir],
  173. ["--dev-path", cfg.devPath],
  174. ].reduce((final, argSet) => {
  175. if (argSet[1]) {
  176. return final.concat(argSet);
  177. } else {
  178. return final;
  179. }
  180. }, []);
  181. console.log("===== installing any additional needed deps =====");
  182. await install({
  183. appDir: appDirectory,
  184. dependencies: recipe.extraNpmDependencies,
  185. devDependencies: ["@tauri-apps/cli", ...recipe.extraNpmDevDependencies],
  186. packageManager,
  187. });
  188. console.log("===== running tauri init =====");
  189. const binary = !argv.b ? packageManager : resolve(appDirectory, argv.b);
  190. const runTauriArgs =
  191. packageManager === "npm" && !argv.b
  192. ? ["run", "tauri", "--", "init"]
  193. : ["tauri", "init"];
  194. await shell(binary, [...runTauriArgs, ...initArgs], {
  195. cwd: appDirectory,
  196. });
  197. if (recipe.postInit) {
  198. console.log("===== running final command(s) =====");
  199. await recipe.postInit({
  200. cwd: appDirectory,
  201. cfg,
  202. packageManager,
  203. });
  204. }
  205. }
  206. createTauriApp(process.argv.slice(2)).catch((err) => {
  207. console.error(err);
  208. });