#!/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, } = 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 ====="); 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); });