create-tauri-app.js 6.3 KB

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