create-tauri-app.js 6.2 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. })