#!/usr/bin/env node // Copyright 2019-2021 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT const parseArgs = require('minimist') const inquirer = require('inquirer') const { resolve, join } = require('path') const { recipeShortNames, recipeDescriptiveNames, recipeByDescriptiveName, recipeByShortName, install, checkPackageManager, shell, addTauriScript } = require('../dist/') /** * @type {object} * @property {boolean} h * @property {boolean} help * @property {boolean} v * @property {boolean} version * @property {string|boolean} f * @property {string|boolean} force * @property {boolean} l * @property {boolean} log * @property {boolean} d * @property {boolean} directory * @property {string} r * @property {string} recipe */ const createTauriApp = async (cliArgs) => { const argv = parseArgs(cliArgs, { alias: { h: 'help', v: 'version', f: 'force', l: 'log', m: 'manager', d: 'directory', b: 'binary', t: 'tauri-path', A: 'app-name', W: 'window-title', D: 'dist-dir', P: 'dev-path', r: 'recipe' }, boolean: ['h', 'l', 'ci'] }) if (argv.help) { printUsage() return 0 } if (argv.v) { console.log(require('../package.json').version) return false // do this for node consumers and tests } if (argv.ci) { return runInit(argv) } else { return getOptionsInteractive(argv).then((responses) => runInit(argv, responses) ) } } function printUsage() { console.log(` Description Starts a new tauri app from a "recipe" or pre-built template. Usage $ yarn create tauri-app # npm create-tauri-app Options --help, -h Displays this message -v, --version Displays the Tauri CLI version --ci Skip prompts --force, -f Force init to overwrite [conf|template|all] --log, -l Logging [boolean] --manager, -d Set package manager to use [npm|yarn] --directory, -d Set target directory for init --binary, -b Optional path to a tauri binary from which to run init --app-name, -A Name of your Tauri application --window-title, -W Window title of your Tauri application --dist-dir, -D Web assets location, relative to /src-tauri --dev-path, -P Url of your dev server --recipe, -r Add UI framework recipe. None by default. Supported recipes: [${recipeShortNames.join('|')}] `) } const getOptionsInteractive = (argv) => { let defaultAppName = argv.A || 'tauri-app' return inquirer .prompt([ { type: 'input', name: 'appName', message: 'What is your app name?', default: defaultAppName, when: !argv.A }, { type: 'input', name: 'tauri.window.title', message: 'What should the window title be?', default: 'Tauri App', when: () => !argv.W }, { type: 'list', name: 'recipeName', message: 'Would you like to add a UI recipe?', choices: recipeDescriptiveNames, default: 'No recipe', when: () => !argv.r } ]) .catch((error) => { if (error.isTtyError) { // Prompt couldn't be rendered in the current environment console.log( 'It appears your terminal does not support interactive prompts. Using default values.' ) runInit() } else { // Something else went wrong console.error('An unknown error occurred:', error) } }) } async function runInit(argv, config = {}) { const { appName, recipeName, tauri: { window: { title } } } = config // this little fun snippet pulled from vite determines the package manager the script was run from const packageManager = /yarn/.test(process.env.npm_execpath) ? 'yarn' : 'npm' let recipe if (recipeName !== undefined) { recipe = recipeByDescriptiveName(recipeName) } else if (argv.r) { recipe = recipeByShortName(argv.r) } let buildConfig = { distDir: argv.D, devPath: argv.P } if (recipe !== undefined) { buildConfig = recipe.configUpdate({ buildConfig, packageManager }) } const directory = argv.d || process.cwd() const cfg = { ...buildConfig, appName: appName || argv.A, windowTitle: title || argv.w } // note that our app directory is reliant on the appName and // generally there are issues if the path has spaces (see Windows) // future TODO prevent app names with spaces or escape here? const appDirectory = join(directory, cfg.appName) // this throws an error if we can't run the package manager they requested await checkPackageManager({ cwd: directory, packageManager }) if (recipe.preInit) { console.log('===== running initial command(s) =====') await recipe.preInit({ cwd: directory, cfg, packageManager }) } const initArgs = [ ['--app-name', cfg.appName], ['--window-title', cfg.windowTitle], ['--dist-dir', cfg.distDir], ['--dev-path', cfg.devPath] ].reduce((final, argSet) => { if (argSet[1]) { return final.concat(argSet) } else { return final } }, []) // Vue CLI plugin automatically runs these if (recipe.shortName !== 'vuecli') { console.log('===== installing any additional needed deps =====') await install({ appDir: appDirectory, dependencies: recipe.extraNpmDependencies, devDependencies: ['@tauri-apps/cli', ...recipe.extraNpmDevDependencies], packageManager }) console.log('===== running tauri init =====') addTauriScript(appDirectory) const binary = !argv.b ? packageManager : resolve(appDirectory, argv.b) const runTauriArgs = packageManager === 'npm' && !argv.b ? ['run', 'tauri', '--', 'init'] : ['tauri', 'init'] await shell(binary, [...runTauriArgs, ...initArgs], { cwd: appDirectory }) } if (recipe.postInit) { console.log('===== running final command(s) =====') await recipe.postInit({ cwd: appDirectory, cfg, packageManager }) } } createTauriApp(process.argv.slice(2)).catch((err) => { console.error(err) })