Parcourir la source

fix #920: Create recipes (#930)

* Create recipes

A recipe consists of:
	* Name
	* List of NPM dependencies required (dev and production)
	* Sub-commands needed
		For example, the react recipes run create-react-app to set
		up the UI. Future recipes can do anything, they could pull
		down a sample UI, clone a repository, download a static HTML
		file, initialize another environment that compiles to JS/wasm etc.

The devPath and dist folders nomally configured by the init command
are overridden by the recipe definition.

* Fix no recipe prompts

* Fix linter unsafe assignment error

* Add `beforeXCommand` settings to recipes

This is so good, it makes it possible to just run:

> yarn init
> yarn add tauri
> yarn tauri init # with recipe selected, adds app ui
> yarn tauri dev # starts app ui dev server and rust component

* Rename init to create

Also improve so it exports a function that can be passed
arguments, instead of just reading directly from command line

* Add new tauri init command aliased to create

* Update changes file

* Remove unneeded import

* Add customized splash page for react recipes

* Remove unneeded recipe buildconfig property

* Add no-browser setting to start react dev server

* Revert "Add no-browser setting to start react dev server"

This reverts commit 22bcf3ac5a0474b206826417cf314a4be70324c5.
Zak Patterson il y a 5 ans
Parent
commit
43a8c4d2bc

+ 13 - 0
.changes/920-feature-add-recipes-to-init.md

@@ -0,0 +1,13 @@
+---
+"tauri.js": minor
+---
+
+* Break out TauriBuildConfig interface from TauriConfig build property
+* Create recipes. A recipe:
+	* Updates the TauriBuildConfig during the init process
+	* Specifies npm dev and production dependencies to be installed
+	* Runs extra installation scripts
+* Create React JS and React TS recipes
+* Add new top level command `create`, which accepts a recipe as a CLI, or runs 
+interactively, prompting for a recipe out of a menu of choices defined by `api/recipes/index`
+* Refactor `init` command so that it is just an alias for `create` with no recipe

+ 204 - 0
cli/tauri.js/bin/tauri-create.js

@@ -0,0 +1,204 @@
+const parseArgs = require('minimist')
+const inquirer = require('inquirer')
+const { resolve } = require('path')
+const { merge } = require('lodash')
+const { 
+  recipeShortNames, 
+  recipeDescriptiveNames, 
+  recipeByDescriptiveName, 
+  recipeByShortName 
+} = require('../dist/api/recipes')
+
+/**
+ * @type {object}
+ * @property {boolean} h
+ * @property {boolean} help
+ * @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
+ */
+function main(cliArgs) {
+  const argv = parseArgs(cliArgs, {
+    alias: {
+      h: 'help',
+      f: 'force',
+      l: 'log',
+      d: 'directory',
+      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.ci) {
+    runInit(argv)
+  } else {
+    getOptionsInteractive(argv).then(responses => runInit(argv, responses))
+  }
+}
+
+function printUsage() {
+  console.log(`
+  Description
+    Inits the Tauri template. If Tauri cannot find the tauri.conf.json
+    it will create one.
+  Usage
+    $ tauri create
+  Options
+    --help, -h           Displays this message
+    --ci                 Skip prompts
+    --force, -f          Force init to overwrite [conf|template|all]
+    --log, -l            Logging [boolean]
+    --directory, -d      Set target directory for init
+    --tauri-path, -t     Path of the Tauri project to use (relative to the cwd)
+    --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 <project-dir>/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
+  if (!defaultAppName) {
+    try {
+      const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json')).toString())
+      defaultAppName = packageJson.displayName || packageJson.name
+    } catch {}
+  }
+
+  return inquirer
+    .prompt([{
+        type: 'input',
+        name: 'appName',
+        message: 'What is your app name?',
+        default: defaultAppName
+      }, {
+        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
+      }
+    ])
+    .then(answers =>
+      inquirer
+        .prompt([
+          {
+            type: 'input',
+            name: 'build.devPath',
+            message: 'What is the url of your dev server?',
+            default: 'http://localhost:4000',
+            when: () => !argv.P && !argv.p && answers.recipeName === 'No recipe' || argv.r === 'none'
+          },
+          {
+            type: 'input',
+            name: 'build.distDir',
+            message: 'Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri" folder that will be created?',
+            default: '../dist',
+            when: () => !argv.D && answers.recipeName === 'No recipe' || argv.r === 'none'
+          }
+        ])
+        .then(answers2 => ({...answers, ...answers2}))
+    )
+    .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 when wrong
+        console.error('An unknown error occurred:', error)
+      }
+    })
+}
+
+
+async function runInit(argv, config = {}) {
+  const {
+    appName,
+    recipeName,
+    ...configOptions
+  } = config
+  const init = require('../dist/api/init')
+
+  let recipe;
+  let recipeSelection = 'none'
+
+  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) {
+    recipeSelection = recipe.shortName
+    buildConfig = recipe.configUpdate(buildConfig)
+  }
+
+  const directory = argv.d || process.cwd()
+  
+  init({
+    directory,
+    force: argv.f || null,
+    logging: argv.l || null,
+    tauriPath: argv.t || null,
+    appName: appName || argv.A || null,
+    customConfig: merge(configOptions, {
+      build: buildConfig,
+      tauri: {
+        window: {
+          title: argv.W
+        }
+      }
+    })
+  })
+   
+  const {
+    installDependencies
+  } = require('../dist/api/dependency-manager')
+  await installDependencies()
+  
+  if (recipe !== undefined) {
+    const {
+      installRecipeDependencies,
+      runRecipePostConfig
+    } = require('../dist/api/recipes/install')
+  
+    await installRecipeDependencies(recipe, directory)
+    await runRecipePostConfig(recipe, directory)
+  }
+}
+
+module.exports = main

+ 23 - 107
cli/tauri.js/bin/tauri-init.js

@@ -1,9 +1,10 @@
 const parseArgs = require('minimist')
-const inquirer = require('inquirer')
-const { resolve } = require('path')
-const { merge } = require('lodash')
+const tauriCreate = require('./tauri-create')
 
 /**
+ * init is an alias for create -r none, same as 
+ * creating a fresh tauri project with no UI recipe applied.
+ * 
  * @type {object}
  * @property {boolean} h
  * @property {boolean} help
@@ -14,22 +15,25 @@ const { merge } = require('lodash')
  * @property {boolean} d
  * @property {boolean} directory
  */
-const argv = parseArgs(process.argv.slice(2), {
-  alias: {
-    h: 'help',
-    f: 'force',
-    l: 'log',
-    d: 'directory',
-    t: 'tauri-path',
-    A: 'app-name',
-    W: 'window-title',
-    D: 'dist-dir',
-    P: 'dev-path'
-  },
-  boolean: ['h', 'l', 'ci']
-})
+function main(cliArgs) {
+  const argv = parseArgs(cliArgs, {
+    alias: {
+      h: 'help',
+    },
+    boolean: ['h']
+  })
+
+  if (argv.help) {
+    printUsage()
+    process.exit(0)
+  }
+
+  // delegate actual work to create command
+  tauriCreate([...cliArgs, '-r', 'none'])
+}
 
-if (argv.help) {
+
+function printUsage() {
   console.log(`
   Description
     Inits the Tauri template. If Tauri cannot find the tauri.conf.json
@@ -48,94 +52,6 @@ if (argv.help) {
     --dist-dir, -D       Web assets location, relative to <project-dir>/src-tauri
     --dev-path, -P       Url of your dev server
     `)
-  process.exit(0)
-}
-
-let appName = argv.A
-if (!appName) {
-  try {
-    const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json')).toString())
-    appName = packageJson.displayName || packageJson.name
-  } catch {}
 }
 
-if (argv.ci) {
-  runInit()
-} else {
-  inquirer
-    .prompt([{
-        type: 'input',
-        name: 'appName',
-        message: 'What is your app name?',
-        default: appName
-      }, {
-        type: 'input',
-        name: 'tauri.window.title',
-        message: 'What should the window title be?',
-        default: 'Tauri App',
-        when: () => !argv.W
-      },
-      {
-        type: 'input',
-        name: 'build.distDir',
-        message: 'Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri" folder that will be created?',
-        default: '../dist',
-        when: () => !argv.D
-      },
-      {
-        type: 'input',
-        name: 'build.devPath',
-        message: 'What is the url of your dev server?',
-        default: 'http://localhost:4000',
-        when: () => !argv.P
-      }
-    ])
-    .then(answers => {
-      runInit(answers)
-    })
-    .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 when wrong
-        console.error('An unknown error occurred:', error)
-      }
-    })
-}
-
-async function runInit(config = {}) {
-  const {
-    appName,
-    ...configOptions
-  } = config
-  const init = require('../dist/api/init')
-
-  const directory = argv.d || process.cwd()
-  init({
-    directory,
-    force: argv.f || null,
-    logging: argv.l || null,
-    tauriPath: argv.t || null,
-    appName: appName || argv.A || null,
-    customConfig: merge(configOptions, {
-      build: {
-        distDir: argv.D,
-        devPath: argv.P
-      },
-      tauri: {
-        window: {
-          title: argv.W
-        }
-      }
-    })
-  })
-
-  const {
-    installDependencies
-  } = require('../dist/api/dependency-manager')
-  await installDependencies()
-}
+module.exports = main

+ 6 - 2
cli/tauri.js/bin/tauri.js

@@ -1,6 +1,6 @@
 #!/usr/bin/env node
 
-const cmds = ['init', 'dev', 'build', 'help', 'icon', 'info', 'deps']
+const cmds = ['create', 'init', 'dev', 'build', 'help', 'icon', 'info', 'deps']
 
 const cmd = process.argv[2]
 /**
@@ -40,7 +40,11 @@ const tauri = function (command) {
     }
     console.log(`[tauri]: running ${command}`)
     // eslint-disable-next-line security/detect-non-literal-require
-    require(`./tauri-${command}`)
+    if (['create', 'init'].includes(command)) {
+      require(`./tauri-${command}`)(process.argv.slice(2))
+    } else {
+      require(`./tauri-${command}`)
+    }
   } else {
     console.log(`Invalid command ${command}. Use one of ${cmds.join(',')}.`)
   }

+ 28 - 7
cli/tauri.js/src/api/dependency-manager/npm-packages.ts

@@ -1,5 +1,12 @@
 import { ManagementType, Result } from './types'
-import { getNpmLatestVersion, getNpmPackageVersion, installNpmPackage, updateNpmPackage, semverLt } from './util'
+import {
+  getNpmLatestVersion,
+  getNpmPackageVersion,
+  installNpmPackage,
+  installNpmDevPackage,
+  updateNpmPackage,
+  semverLt
+} from './util'
 import logger from '../../helpers/logger'
 import { resolve } from '../../helpers/app-paths'
 import inquirer from 'inquirer'
@@ -7,9 +14,7 @@ import { existsSync } from 'fs'
 
 const log = logger('dependency:npm-packages')
 
-const dependencies = ['tauri']
-
-async function manageDependencies(managementType: ManagementType): Promise<Result> {
+async function manageDependencies(managementType: ManagementType, dependencies: string[]): Promise<Result> {
   const installedDeps = []
   const updatedDeps = []
 
@@ -18,7 +23,11 @@ async function manageDependencies(managementType: ManagementType): Promise<Resul
       const currentVersion = await getNpmPackageVersion(dependency)
       if (currentVersion === null) {
         log(`Installing ${dependency}...`)
-        installNpmPackage(dependency)
+        if (managementType === ManagementType.Install) {
+          installNpmPackage(dependency)
+        } else if (managementType === ManagementType.InstallDev) {
+          installNpmDevPackage(dependency)
+        }
         installedDeps.push(dependency)
       } else if (managementType === ManagementType.Update) {
         const latestVersion = getNpmLatestVersion(dependency)
@@ -50,15 +59,27 @@ async function manageDependencies(managementType: ManagementType): Promise<Resul
   return result
 }
 
+const dependencies = ['tauri']
+
 async function install(): Promise<Result> {
-  return await manageDependencies(ManagementType.Install)
+  return await manageDependencies(ManagementType.Install, dependencies)
+}
+
+async function installThese(dependencies: string[]): Promise<Result> {
+  return await manageDependencies(ManagementType.Install, dependencies)
+}
+
+async function installTheseDev(dependencies: string[]): Promise<Result> {
+  return await manageDependencies(ManagementType.InstallDev, dependencies)
 }
 
 async function update(): Promise<Result> {
-  return await manageDependencies(ManagementType.Update)
+  return await manageDependencies(ManagementType.Update, dependencies)
 }
 
 export {
   install,
+  installThese,
+  installTheseDev,
   update
 }

+ 1 - 0
cli/tauri.js/src/api/dependency-manager/types.ts

@@ -1,5 +1,6 @@
 export enum ManagementType {
   Install,
+  InstallDev,
   Update
 }
 

+ 10 - 0
cli/tauri.js/src/api/dependency-manager/util.ts

@@ -50,6 +50,15 @@ function installNpmPackage(packageName: string): void {
   }
 }
 
+function installNpmDevPackage(packageName: string): void {
+  const usesYarn = existsSync(appResolve.app('yarn.lock'))
+  if (usesYarn) {
+    spawnSync('yarn', ['add', packageName, '--dev'], appDir)
+  } else {
+    spawnSync('npm', ['install', packageName, '--save-dev'], appDir)
+  }
+}
+
 function updateNpmPackage(packageName: string): void {
   const usesYarn = existsSync(appResolve.app('yarn.lock'))
   if (usesYarn) {
@@ -77,6 +86,7 @@ export {
   getNpmLatestVersion,
   getNpmPackageVersion,
   installNpmPackage,
+  installNpmDevPackage,
   updateNpmPackage,
   padVersion,
   semverLt

+ 40 - 0
cli/tauri.js/src/api/recipes/index.ts

@@ -0,0 +1,40 @@
+import { map, identity, find } from 'lodash'
+import { TauriBuildConfig } from '../../types/config'
+import { reactjs, reactts } from './react'
+
+export interface Recipe {
+  descriptiveName: string
+  shortName: string
+  configUpdate: (cfg: TauriBuildConfig) => TauriBuildConfig
+  extraNpmDependencies: string[]
+  extraNpmDevDependencies: string[]
+  postConfiguration: (cwd: string) => void
+}
+
+const none = {
+  descriptiveName: 'No recipe',
+  shortName: 'none',
+  configUpdate: identity,
+  extraNpmDependencies: [],
+  extraNpmDevDependencies: [],
+  postConfiguration: (cwd: string) => {}
+}
+
+export const allRecipes: Recipe[] = [
+  none,
+  reactjs,
+  reactts
+]
+
+export const recipeNames: Array<[string, string]> =
+  map(allRecipes, (r: Recipe) => [r.shortName, r.descriptiveName])
+
+export const recipeByShortName = (name: string): Recipe | undefined =>
+  find(allRecipes, (r: Recipe) => r.shortName === name)
+
+export const recipeByDescriptiveName = (name: string): Recipe | undefined =>
+  find(allRecipes, (r: Recipe) => r.descriptiveName === name)
+
+export const recipeShortNames: string[] = map(allRecipes, (r: Recipe) => r.shortName)
+
+export const recipeDescriptiveNames: string[] = map(allRecipes, (r: Recipe) => r.descriptiveName)

+ 22 - 0
cli/tauri.js/src/api/recipes/install.ts

@@ -0,0 +1,22 @@
+import { installThese, installTheseDev } from '../dependency-manager/npm-packages'
+import { Recipe } from '.'
+import { Result } from '../dependency-manager/types'
+import logger from '../../helpers/logger'
+
+export async function installRecipeDependencies(recipe: Recipe): Promise<Result> {
+  const log = logger('recipe:install')
+
+  log(`Installing dependencies for ${recipe.descriptiveName}`)
+  return await installThese(recipe.extraNpmDependencies).then(async (results) =>
+    await installTheseDev(recipe.extraNpmDevDependencies).then((results2) =>
+      new Map([...Array.from(results.entries()), ...Array.from(results2.entries())])
+    )
+  )
+}
+
+export async function runRecipePostConfig(recipe: Recipe, cwd: string): Promise<void> {
+  const log = logger('recipe:postconfig')
+
+  log(`Running post configuration for ${recipe.descriptiveName}`)
+  return await new Promise(() => recipe.postConfiguration(cwd))
+}

+ 58 - 0
cli/tauri.js/src/api/recipes/react.ts

@@ -0,0 +1,58 @@
+import { Recipe } from '.'
+import { TauriBuildConfig } from '../../types/config'
+import { spawnSync } from '../../helpers/spawn'
+import logger from '../../helpers/logger'
+import copyTemplates from '../../helpers/copy-templates'
+import { resolve, join } from 'path'
+
+const uiAppDir = 'app-ui'
+
+const log = logger('react-recipe')
+
+const completeLogMsg = `
+  Your installation completed.
+  To start, run yarn tauri dev
+`
+
+const afterCra = (): void => {
+  copyTemplates({
+    source: resolve(__dirname, '../../templates/recipes/react/'),
+    scope: {},
+    target: join(uiAppDir, './src/')
+  })
+  log(completeLogMsg)
+}
+
+const reactjs: Recipe = {
+  descriptiveName: 'React.js',
+  shortName: 'reactjs',
+  configUpdate: (cfg: TauriBuildConfig): TauriBuildConfig => ({
+    ...cfg,
+    distDir: `../${uiAppDir}/build`,
+    devPath: 'http://localhost:3000',
+    beforeDevCommand: `yarn --cwd ${uiAppDir} start`,
+    beforeBuildCommand: `yarn --cwd ${uiAppDir} build`
+  }),
+  extraNpmDevDependencies: ['create-react-app'],
+  extraNpmDependencies: ['react'],
+  postConfiguration: (cwd: string) => {
+    spawnSync('yarn', ['create-react-app', uiAppDir], cwd)
+    afterCra()
+  }
+}
+
+const reactts: Recipe = {
+  ...reactjs,
+  descriptiveName: 'React with Typescript',
+  shortName: 'reactts',
+  extraNpmDependencies: ['typescript', '@types/node', '@types/react', '@types/react-dom', '@types/jest'],
+  postConfiguration: (cwd: string) => {
+    spawnSync('yarn', ['create-react-app', '--template', 'typescript', uiAppDir], cwd)
+    afterCra()
+  }
+}
+
+export {
+  reactjs,
+  reactts
+}

+ 9 - 6
cli/tauri.js/src/types/config.schema.json

@@ -194,15 +194,11 @@
         }
       },
       "type": "object"
-    }
-  },
-  "description": "Tauri configuration",
-  "properties": {
-    "build": {
+    },
+    "TauriBuildConfig": {
       "additionalProperties": false,
       "defaultProperties": [
       ],
-      "description": "build/dev configuration",
       "properties": {
         "beforeBuildCommand": {
           "description": "a shell command to run before `tauri build` kicks in",
@@ -229,6 +225,13 @@
         "distDir"
       ],
       "type": "object"
+    }
+  },
+  "description": "Tauri configuration",
+  "properties": {
+    "build": {
+      "$ref": "#/definitions/TauriBuildConfig",
+      "description": "build/dev configuration"
     },
     "ctx": {
       "additionalProperties": false,

+ 22 - 20
cli/tauri.js/src/types/config.ts

@@ -155,6 +155,27 @@ export interface CliConfig {
   subcommands?: { [name: string]: CliConfig }
 }
 
+export interface TauriBuildConfig {
+  /**
+   * the path to the app's dist dir
+   * this path must contain your index.html file
+   */
+  distDir: string
+  /**
+   * the app's dev server URL, or the path to the directory containing an index.html to open
+   */
+  devPath: string
+  /**
+   * a shell command to run before `tauri dev` kicks in
+   */
+  beforeDevCommand?: string
+  /**
+   * a shell command to run before `tauri build` kicks in
+   */
+  beforeBuildCommand?: string
+  withGlobalTauri?: boolean
+}
+
 /**
  * Tauri configuration
  */
@@ -162,26 +183,7 @@ export interface TauriConfig {
   /**
    * build/dev configuration
    */
-  build: {
-    /**
-     * the path to the app's dist dir
-     * this path must contain your index.html file
-     */
-    distDir: string
-    /**
-     * the app's dev server URL, or the path to the directory containing an index.html to open
-     */
-    devPath: string
-    /**
-     * a shell command to run before `tauri dev` kicks in
-     */
-    beforeDevCommand?: string
-    /**
-     * a shell command to run before `tauri build` kicks in
-     */
-    beforeBuildCommand?: string
-    withGlobalTauri?: boolean
-  }
+  build: TauriBuildConfig
   /**
    * the context of the current `tauri dev` or `tauri build`
    */

+ 9 - 6
cli/tauri.js/src/types/config.validator.ts

@@ -204,15 +204,11 @@ export const TauriConfigSchema = {
         }
       },
       "type": "object"
-    }
-  },
-  "description": "Tauri configuration",
-  "properties": {
-    "build": {
+    },
+    "TauriBuildConfig": {
       "additionalProperties": false,
       "defaultProperties": [
       ],
-      "description": "build/dev configuration",
       "properties": {
         "beforeBuildCommand": {
           "description": "a shell command to run before `tauri build` kicks in",
@@ -239,6 +235,13 @@ export const TauriConfigSchema = {
         "distDir"
       ],
       "type": "object"
+    }
+  },
+  "description": "Tauri configuration",
+  "properties": {
+    "build": {
+      "$ref": "#/definitions/TauriBuildConfig",
+      "description": "build/dev configuration"
     },
     "ctx": {
       "additionalProperties": false,

+ 54 - 0
cli/tauri.js/templates/recipes/react/App.css

@@ -0,0 +1,54 @@
+.App {
+  text-align: center;
+}
+
+.App-logo {
+  height: 20vmin;
+  pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  .App-logo.rotate {
+    animation: App-logo-spin infinite 20s linear;
+  }
+}
+
+div.inline-logo {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+}
+
+div.inline-logo > img {
+  margin-right: 3vw;
+}
+
+div.inline-logo .smaller {
+  height: 10vh;
+}
+
+.App-header {
+  background-color: #282c34;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  font-size: calc(10px + 2vmin);
+  color: white;
+}
+
+.App-link {
+  color: #61dafb;
+  margin-bottom: 5vh;
+}
+
+@keyframes App-logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 40 - 0
cli/tauri.js/templates/recipes/react/App.js

@@ -0,0 +1,40 @@
+import React from 'react';
+import logo from './logo.svg';
+import tauriCircles from './tauri.svg';
+import tauriWord from './wordmark.svg';
+import './App.css';
+
+function App() {
+  return (
+    <div className="App">
+      <header className="App-header">
+        <div className="inline-logo">
+          <img src={tauriCircles} className="App-logo rotate" alt="logo" />
+          <img src={tauriWord} className="App-logo smaller" alt="logo" />
+        </div>
+        <a
+          className="App-link"
+          href="https://tauri.studio"
+          target="_blank"
+          rel="noopener noreferrer"
+          >
+          Learn Tauri
+        </a>
+        <img src={logo} className="App-logo rotate" alt="logo" />
+        <a
+          className="App-link"
+          href="https://reactjs.org"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          Learn React
+        </a>
+        <p>
+          Edit <code>src/App.js</code> and save to reload.
+        </p>
+      </header>
+    </div>
+  );
+}
+
+export default App;

+ 21 - 0
cli/tauri.js/templates/recipes/react/tauri.svg

@@ -0,0 +1,21 @@
+<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" version="1.1" viewBox="0 0 383.44479 435.98273" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
+    <g transform="translate(-703.62301,-1339.6611)">
+            <g transform="rotate(-19.354322)">
+                <circle cx="274.08301" cy="1813.09" r="32" fill="url(#d)"/>
+            </g>
+            <g transform="rotate(-19.354322)">
+                <circle cx="382.97299" cy="1719.61" r="32" fill="url(#c)"/>
+            </g>
+            <path d="m796.022 1418.15c-21.659 37.92-27.401 84.66-11.828 129 4.38 12.47 10.212 24.01 17.214 34.55 1.051 1.88 2.59 3.45 4.455 4.53 5.701 3.29 13.101 1.31 16.392-4.39 2.286-3.97 2.104-8.92-0.468-12.72l0.027-0.02c-6.097-9.09-11.178-19.08-14.98-29.9-24.177-68.83 11.861-143.9 80.692-168.08s143.904 11.86 168.084 80.69-11.87 143.91-80.699 168.09c-17.276 6.06-34.942 8.32-52.099 7.23h-0.052c-4.759-0.57-9.423 1.76-11.82 5.91-3.291 5.71-1.309 13.1 4.392 16.4 1.905 1.09 4.073 1.64 6.268 1.59 20.21 1.28 40.988-1.37 61.264-8.49 81.066-28.48 123.866-117.61 95.386-198.68s-117.609-123.86-198.678-95.38c-36.734 12.9-65.607 38.26-83.553 69.67z" fill="url(#b)" fill-rule="nonzero"/>
+            <path d="m724.265 1542.44c-21.659 37.92-27.397 84.66-11.824 129 28.476 81.07 117.602 123.86 198.67 95.39 81.069-28.48 123.859-117.61 95.389-198.68-4.33-12.34-10.09-23.77-16.991-34.21-1.05-2.05-2.668-3.75-4.659-4.91-5.701-3.29-13.101-1.31-16.392 4.39-2.287 3.98-2.105 8.93 0.467 12.72l-0.058 0.04c6.101 9.1 11.186 19.09 14.989 29.92 24.174 68.83-11.866 143.91-80.697 168.08-68.831 24.18-143.899-11.86-168.076-80.7-24.178-68.83 11.859-143.9 80.69-168.08 17.493-6.14 35.388-8.39 52.75-7.2l1e-3 -0.03c4.572 0.33 8.949-1.99 11.246-5.95 3.291-5.7 1.309-13.1-4.392-16.39-2.026-1.17-4.349-1.72-6.682-1.58-20.088-1.23-40.73 1.43-60.877 8.51-36.734 12.9-65.609 38.26-83.554 69.67z" fill="url(#a)" fill-rule="nonzero"/>
+        </g>
+    <defs>
+        <linearGradient id="d" x2="1" gradientTransform="matrix(48.6643,-41.7777,41.7777,48.6643,249.699,1834.02)" gradientUnits="userSpaceOnUse"><stop stop-color="#0096f2" offset="0"/><stop stop-color="#4cffc4" offset="1"/></linearGradient>
+        <linearGradient id="c" x2="1" gradientTransform="matrix(-48.5635,41.6911,-41.6911,-48.5635,407.745,1699.34)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff8a11" offset="0"/><stop stop-color="#fff550" offset="1"/></linearGradient>
+        <linearGradient id="b" x2="1" gradientTransform="matrix(-150.612,260.867,-260.867,-150.612,960.685,1332.65)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff8a11" offset="0"/><stop stop-color="#fff550" offset="1"/></linearGradient>
+        <linearGradient id="a" x2="1" gradientTransform="matrix(150.613,-260.87,260.87,150.613,781.584,1754.69)" gradientUnits="userSpaceOnUse"><stop stop-color="#0096f2" offset="0"/><stop stop-color="#4cffc4" offset="1"/></linearGradient>
+    
+        
+        
+    </defs>
+</svg>

Fichier diff supprimé car celui-ci est trop grand
+ 4 - 0
cli/tauri.js/templates/recipes/react/wordmark.svg


+ 2 - 0
cli/tauri.js/webpack.config.js

@@ -7,6 +7,8 @@ module.exports = {
     'api/build': './src/api/build.ts',
     'api/dev': './src/api/dev.ts',
     'api/init': './src/api/init.ts',
+    'api/recipes': './src/api/recipes/index.ts',
+    'api/recipes/install': './src/api/recipes/install.ts',
     'api/tauricon': './src/api/tauricon.ts',
     'api/info': './src/api/info.ts',
     'api/dependency-manager': './src/api/dependency-manager/index.ts',

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff