create-tauri-app.js 6.7 KB

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