소스 검색

chore: shift CTA from bin to .ts (#1651)

* chore: shift CTA from bin to .ts

* add change file

* fix rollup build
Jacob Bolda 4 년 전
부모
커밋
c3acbd68ec

+ 5 - 0
.changes/cta-shift-and-type.md

@@ -0,0 +1,5 @@
+---
+"create-tauri-app": patch
+---
+
+Shift everything out of the `bin` and into `.ts` so we can apply Typescript types.

+ 1 - 1
tooling/create-tauri-app/.eslintrc.js

@@ -7,7 +7,7 @@ module.exports = {
   },
 
   parser: '@typescript-eslint/parser',
-  ignorePatterns: ['.eslintrc.js', 'jest.config.js', 'test/**/*'],
+  ignorePatterns: ['.eslintrc.js', '*.config.js', 'test', 'bin'],
   extends: [
     'standard-with-typescript',
     'plugin:@typescript-eslint/recommended-requiring-type-checking',

+ 1 - 243
tooling/create-tauri-app/bin/create-tauri-app.js

@@ -3,249 +3,7 @@
 // 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 {boolean} dev
- * @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',
-      dev: 'dev',
-      t: 'tauri-path',
-      A: 'app-name',
-      W: 'window-title',
-      D: 'dist-dir',
-      P: 'dev-path',
-      r: 'recipe'
-    },
-    boolean: ['h', 'l', 'ci', 'dev']
-  })
-
-  if (argv.help) {
-    printUsage()
-    return 0
-  }
-
-  if (argv.v) {
-    console.log(require('../package.json').version)
-    return false // do this for node consumers and tests
-  }
-
-  return getOptionsInteractive(argv, !argv.ci).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 <app-name> # npm create-tauri-app <app-name>
-  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 <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, ask) => {
-  const defaults = {
-    appName: argv.A || 'tauri-app',
-    tauri: { window: { title: 'Tauri App' } },
-    recipeName: argv.r || 'vanillajs'
-  }
-
-  return inquirer
-    .prompt([
-      {
-        type: 'input',
-        name: 'appName',
-        message: 'What is your app name?',
-        default: defaults.appName,
-        when: ask && !argv.A
-      },
-      {
-        type: 'input',
-        name: 'tauri.window.title',
-        message: 'What should the window title be?',
-        default: defaults.tauri.window.title,
-        when: ask && !argv.W
-      },
-      {
-        type: 'list',
-        name: 'recipeName',
-        message: 'Would you like to add a UI recipe?',
-        choices: recipeDescriptiveNames,
-        default: defaults.recipeName,
-        when: ask && !argv.r
-      }
-    ])
-    .then((answers) => ({
-      ...defaults,
-      ...answers
-    }))
-    .catch((error) => {
-      if (error.isTtyError) {
-        // Prompt couldn't be rendered in the current environment
-        console.warn(
-          '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 (argv.r) {
-    recipe = recipeByShortName(argv.r)
-  } else if (recipeName !== undefined) {
-    recipe = recipeByDescriptiveName(recipeName)
-  }
-
-  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 =====')
-    if (argv.dev) {
-      await shell('yarn', ['link', '@tauri-apps/cli'], {
-        cwd: appDirectory
-      })
-      await shell('yarn', ['link', '@tauri-apps/api'], {
-        cwd: appDirectory
-      })
-    }
-
-    await install({
-      appDir: appDirectory,
-      dependencies: recipe.extraNpmDependencies,
-      devDependencies: argv.dev
-        ? [...recipe.extraNpmDevDependencies]
-        : ['@tauri-apps/cli'].concat(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, '--ci'], {
-      cwd: appDirectory
-    })
-  }
-
-  if (recipe.postInit) {
-    console.log('===== running final command(s) =====')
-    await recipe.postInit({
-      cwd: appDirectory,
-      cfg,
-      packageManager
-    })
-  }
-}
+const { createTauriApp } = require('../dist/')
 
 createTauriApp(process.argv.slice(2)).catch((err) => {
   console.error(err)

+ 0 - 2
tooling/create-tauri-app/jest.config.js

@@ -6,8 +6,6 @@ module.exports = {
   preset: 'ts-jest',
   testEnvironment: 'node',
   modulePathIgnorePatterns: ['__fixtures__'],
-  testMatch: ['<rootDir>/test/**/*.spec.ts'],
-  moduleFileExtensions: ['ts', 'js', 'json'],
   globals: {
     'ts-jest': {
       tsconfig: 'tsconfig.json'

+ 1 - 0
tooling/create-tauri-app/package.json

@@ -45,6 +45,7 @@
     "@types/cross-spawn": "6.0.2",
     "@types/inquirer": "7.3.1",
     "@types/jest": "26.0.23",
+    "@types/minimist": "1.2.1",
     "@types/semver": "7.3.5",
     "@typescript-eslint/eslint-plugin": "4.22.0",
     "@typescript-eslint/parser": "4.22.0",

+ 282 - 14
tooling/create-tauri-app/src/index.ts

@@ -2,16 +2,175 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+import minimist from 'minimist'
+import inquirer from 'inquirer'
+import { resolve, join } from 'path'
+
 import { TauriBuildConfig } from './types/config'
 import { reactjs, reactts } from './recipes/react'
 import { vuecli } from './recipes/vue-cli'
 import { vanillajs } from './recipes/vanilla'
 import { vite } from './recipes/vite'
-import { PackageManager } from './dependency-manager'
+import {
+  install,
+  checkPackageManager,
+  PackageManager
+} from './dependency-manager'
+
+import { shell } from './shell'
+import { addTauriScript } from './helpers/add-tauri-script'
+
+interface Argv {
+  h: boolean
+  help: boolean
+  v: string
+  version: string
+  ci: boolean
+  dev: boolean
+  b: string
+  binary: string
+  f: string
+  force: string
+  l: boolean
+  log: boolean
+  m: string
+  manager: string
+  d: string
+  directory: string
+  t: string
+  tauriPath: string
+  A: string
+  appName: string
+  W: string
+  windowTitle: string
+  D: string
+  distDir: string
+  P: string
+  devPath: string
+  r: string
+  recipe: string
+}
+
+const printUsage = (): void => {
+  console.log(`
+  Description
+    Starts a new tauri app from a "recipe" or pre-built template.
+  Usage
+    $ yarn create tauri-app <app-name> # npm create-tauri-app <app-name>
+  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
+    --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('|')}]
+    `)
+}
+
+export const createTauriApp = async (cliArgs: string[]): Promise<any> => {
+  const argv = (minimist(cliArgs, {
+    alias: {
+      h: 'help',
+      v: 'version',
+      f: 'force',
+      l: 'log',
+      m: 'manager',
+      d: 'directory',
+      t: 'tauri-path',
+      A: 'app-name',
+      W: 'window-title',
+      D: 'dist-dir',
+      P: 'dev-path',
+      r: 'recipe'
+    },
+    boolean: ['h', 'l', 'ci', 'dev'],
+    default: { A: 'tauri-app', r: 'vanillajs' }
+  }) as unknown) as Argv
+
+  if (argv.help) {
+    printUsage()
+    return 0
+  }
 
-export { shell } from './shell'
-export { install, checkPackageManager } from './dependency-manager'
-export { addTauriScript } from './helpers/add-tauri-script'
+  if (argv.v) {
+    /* eslint-disable @typescript-eslint/no-var-requires */
+    /* eslint-disable @typescript-eslint/no-unsafe-member-access */
+    console.log(require('../package.json').version)
+    return false // do this for node consumers and tests
+    /* eslint-enable @typescript-eslint/no-var-requires */
+    /* eslint-enable @typescript-eslint/no-unsafe-member-access */
+  }
+
+  return await getOptionsInteractive(argv, !argv.ci).then(
+    async (responses) => await runInit(argv, responses)
+  )
+}
+
+interface Responses {
+  appName: string
+  tauri: { window: { title: string } }
+  recipeName: string
+}
+
+const getOptionsInteractive = async (
+  argv: Argv,
+  ask: boolean
+): Promise<Responses> => {
+  const defaults = {
+    appName: argv.A,
+    tauri: { window: { title: 'Tauri App' } },
+    recipeName: argv.r
+  }
+
+  return (await inquirer
+    .prompt([
+      {
+        type: 'input',
+        name: 'appName',
+        message: 'What is your app name?',
+        default: defaults.appName,
+        when: ask && !argv.A
+      },
+      {
+        type: 'input',
+        name: 'tauri.window.title',
+        message: 'What should the window title be?',
+        default: defaults.tauri.window.title,
+        when: ask && !argv.W
+      },
+      {
+        type: 'list',
+        name: 'recipeName',
+        message: 'Would you like to add a UI recipe?',
+        choices: recipeDescriptiveNames,
+        default: defaults.recipeName,
+        when: ask && !argv.r
+      }
+    ])
+    .then((answers: Argv) => ({
+      ...defaults,
+      ...answers
+    }))
+    .catch(async (error: { isTtyError: boolean }) => {
+      if (error.isTtyError) {
+        // Prompt couldn't be rendered in the current environment
+        console.warn(
+          'It appears your terminal does not support interactive prompts. Using default values.'
+        )
+      } else {
+        // Something else went wrong
+        console.error('An unknown error occurred:', error)
+      }
+      return await runInit(argv, defaults)
+    })) as Responses
+}
 
 export interface Recipe {
   descriptiveName: string
@@ -45,19 +204,128 @@ export interface Recipe {
   }) => Promise<void>
 }
 
-export const allRecipes: Recipe[] = [vanillajs, reactjs, reactts, vite, vuecli]
-
-export const recipeNames: Array<[string, string]> = allRecipes.map((r) => [
-  r.shortName,
-  r.descriptiveName
-])
+const allRecipes: Recipe[] = [vanillajs, reactjs, reactts, vite, vuecli]
 
-export const recipeByShortName = (name: string): Recipe | undefined =>
+const recipeByShortName = (name: string): Recipe | undefined =>
   allRecipes.find((r) => r.shortName === name)
 
-export const recipeByDescriptiveName = (name: string): Recipe | undefined =>
+const recipeByDescriptiveName = (name: string): Recipe | undefined =>
   allRecipes.find((r) => r.descriptiveName === name)
 
-export const recipeShortNames = allRecipes.map((r) => r.shortName)
+const recipeShortNames = allRecipes.map((r) => r.shortName)
 
-export const recipeDescriptiveNames = allRecipes.map((r) => r.descriptiveName)
+const recipeDescriptiveNames = allRecipes.map((r) => r.descriptiveName)
+
+const runInit = async (argv: Argv, config: Responses): Promise<void> => {
+  const {
+    appName,
+    recipeName,
+    tauri: {
+      window: { title }
+    }
+  } = config
+  // this little fun snippet pulled from vite determines the package manager the script was run from
+  // @ts-expect-error
+  const packageManager = /yarn/.test(process?.env?.npm_execpath)
+    ? 'yarn'
+    : 'npm'
+
+  let recipe: Recipe | undefined
+
+  if (argv.r) {
+    recipe = recipeByShortName(argv.r)
+  } else if (recipeName !== undefined) {
+    recipe = recipeByDescriptiveName(recipeName)
+  }
+
+  if (!recipe) throw new Error('Could not find the recipe specified.')
+
+  const buildConfig = {
+    distDir: argv.D,
+    devPath: argv.P,
+    appName: appName,
+    windowTitle: title
+  }
+
+  const directory = argv.d || process.cwd()
+  let updatedConfig
+  if (recipe.configUpdate) {
+    updatedConfig = recipe.configUpdate({
+      cfg: buildConfig,
+      packageManager
+    })
+  }
+  const cfg = {
+    ...buildConfig,
+    ...(updatedConfig ?? {})
+  }
+
+  // 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: string[], 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 =====')
+    if (argv.dev) {
+      await shell('yarn', ['link', '@tauri-apps/cli'], {
+        cwd: appDirectory
+      })
+      await shell('yarn', ['link', '@tauri-apps/api'], {
+        cwd: appDirectory
+      })
+    }
+
+    await install({
+      appDir: appDirectory,
+      dependencies: recipe.extraNpmDependencies,
+      devDependencies: argv.dev
+        ? [...recipe.extraNpmDevDependencies]
+        : ['@tauri-apps/cli'].concat(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, '--ci'], {
+      cwd: appDirectory
+    })
+  }
+
+  if (recipe.postInit) {
+    console.log('===== running final command(s) =====')
+    await recipe.postInit({
+      cwd: appDirectory,
+      cfg,
+      packageManager
+    })
+  }
+}

+ 4 - 1
tooling/create-tauri-app/tsconfig.json

@@ -8,7 +8,10 @@
     "esModuleInterop": true,
     "resolveJsonModule": true,
     "moduleResolution": "node",
-    "typeRoots": ["./types", "node_modules/@types"]
+    "checkJs": false,
+    "preserveSymlinks": true,
+    "skipLibCheck": true,
+    "typeRoots": ["types", "node_modules/@types"]
   },
   "include": ["src"],
   "exclude": ["src/templates", "types", "test", "__fixtures__"]