mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-03 00:21:34 +03:00
refactor(cta): add questions hook for recipes (#1658)
* refactor(cta): add questions hook for recipes * fix lint * fix tests * fix vue-cli * one more time * adjust vuecli to packagemanager * revert bundle.js changes * refacotr: extract `Recipe` type to its own file * colorful step logging * pin chalk dependency * Restore bundle.js * fix typo * more styling
This commit is contained in:
parent
b0bb796a42
commit
9fadbf3350
@ -49,6 +49,7 @@
|
||||
"@types/semver": "7.3.5",
|
||||
"@typescript-eslint/eslint-plugin": "4.22.0",
|
||||
"@typescript-eslint/parser": "4.22.0",
|
||||
"chalk": "4.1.1",
|
||||
"eslint": "7.25.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-config-standard-with-typescript": "20.0.0",
|
||||
|
@ -4,21 +4,16 @@
|
||||
|
||||
import minimist from 'minimist'
|
||||
import inquirer from 'inquirer'
|
||||
import { bold, cyan, green, reset, yellow } from 'chalk'
|
||||
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 {
|
||||
install,
|
||||
checkPackageManager,
|
||||
PackageManager
|
||||
} from './dependency-manager'
|
||||
|
||||
import { install, checkPackageManager } from './dependency-manager'
|
||||
import { shell } from './shell'
|
||||
import { addTauriScript } from './helpers/add-tauri-script'
|
||||
import { Recipe } from './types/recipe'
|
||||
|
||||
interface Argv {
|
||||
h: boolean
|
||||
@ -90,8 +85,7 @@ export const createTauriApp = async (cliArgs: string[]): Promise<any> => {
|
||||
P: 'dev-path',
|
||||
r: 'recipe'
|
||||
},
|
||||
boolean: ['h', 'l', 'ci', 'dev'],
|
||||
default: { A: 'tauri-app', r: 'vanillajs' }
|
||||
boolean: ['h', 'l', 'ci', 'dev']
|
||||
}) as unknown) as Argv
|
||||
|
||||
if (argv.help) {
|
||||
@ -108,9 +102,7 @@ export const createTauriApp = async (cliArgs: string[]): Promise<any> => {
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
||||
}
|
||||
|
||||
return await getOptionsInteractive(argv, !argv.ci).then(
|
||||
async (responses) => await runInit(argv, responses)
|
||||
)
|
||||
return await runInit(argv)
|
||||
}
|
||||
|
||||
interface Responses {
|
||||
@ -119,91 +111,6 @@ interface Responses {
|
||||
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
|
||||
shortName: string
|
||||
configUpdate?: ({
|
||||
cfg,
|
||||
packageManager
|
||||
}: {
|
||||
cfg: TauriBuildConfig
|
||||
packageManager: PackageManager
|
||||
}) => TauriBuildConfig
|
||||
extraNpmDependencies: string[]
|
||||
extraNpmDevDependencies: string[]
|
||||
preInit?: ({
|
||||
cwd,
|
||||
cfg,
|
||||
packageManager
|
||||
}: {
|
||||
cwd: string
|
||||
cfg: TauriBuildConfig
|
||||
packageManager: PackageManager
|
||||
}) => Promise<void>
|
||||
postInit?: ({
|
||||
cwd,
|
||||
cfg,
|
||||
packageManager
|
||||
}: {
|
||||
cwd: string
|
||||
cfg: TauriBuildConfig
|
||||
packageManager: PackageManager
|
||||
}) => Promise<void>
|
||||
}
|
||||
|
||||
const allRecipes: Recipe[] = [vanillajs, reactjs, reactts, vite, vuecli]
|
||||
|
||||
const recipeByShortName = (name: string): Recipe | undefined =>
|
||||
@ -216,22 +123,60 @@ const recipeShortNames = allRecipes.map((r) => r.shortName)
|
||||
|
||||
const recipeDescriptiveNames = allRecipes.map((r) => r.descriptiveName)
|
||||
|
||||
const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
const runInit = async (argv: Argv): Promise<void> => {
|
||||
const defaults = {
|
||||
appName: 'tauri-app',
|
||||
tauri: { window: { title: 'Tauri App' } },
|
||||
recipeName: 'vanillajs'
|
||||
}
|
||||
|
||||
// prompt initial questions
|
||||
const answers = (await inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'appName',
|
||||
message: 'What is your app name?',
|
||||
default: defaults.appName,
|
||||
when: !argv.ci && !argv.A
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'tauri.window.title',
|
||||
message: 'What should the window title be?',
|
||||
default: defaults.tauri.window.title,
|
||||
when: !argv.ci && !argv.W
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'recipeName',
|
||||
message: 'Would you like to add a UI recipe?',
|
||||
choices: recipeDescriptiveNames,
|
||||
default: defaults.recipeName,
|
||||
when: !argv.ci && !argv.r
|
||||
}
|
||||
])
|
||||
.catch((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)
|
||||
}
|
||||
})) as Responses
|
||||
|
||||
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'
|
||||
} = { ...defaults, ...answers }
|
||||
|
||||
let recipe: Recipe | undefined
|
||||
|
||||
if (argv.r) {
|
||||
recipe = recipeByShortName(argv.r)
|
||||
} else if (recipeName !== undefined) {
|
||||
@ -240,6 +185,15 @@ const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
|
||||
if (!recipe) throw new Error('Could not find the recipe specified.')
|
||||
|
||||
const packageManager =
|
||||
argv.m === 'yarn' || argv.m === 'npm'
|
||||
? argv.m
|
||||
: // @ts-expect-error
|
||||
// this little fun snippet pulled from vite determines the package manager the script was run from
|
||||
/yarn/.test(process?.env?.npm_execpath)
|
||||
? 'yarn'
|
||||
: 'npm'
|
||||
|
||||
const buildConfig = {
|
||||
distDir: argv.D,
|
||||
devPath: argv.P,
|
||||
@ -248,11 +202,40 @@ const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
}
|
||||
|
||||
const directory = argv.d || process.cwd()
|
||||
|
||||
// prompt additional recipe questions
|
||||
let recipeAnswers
|
||||
if (recipe.extraQuestions) {
|
||||
recipeAnswers = await inquirer
|
||||
.prompt(
|
||||
recipe.extraQuestions({
|
||||
cfg: buildConfig,
|
||||
packageManager,
|
||||
ci: argv.ci,
|
||||
cwd: directory
|
||||
})
|
||||
)
|
||||
.catch((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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let updatedConfig
|
||||
if (recipe.configUpdate) {
|
||||
updatedConfig = recipe.configUpdate({
|
||||
cfg: buildConfig,
|
||||
packageManager
|
||||
packageManager,
|
||||
ci: argv.ci,
|
||||
cwd: directory,
|
||||
answers: recipeAnswers
|
||||
})
|
||||
}
|
||||
const cfg = {
|
||||
@ -269,8 +252,14 @@ const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
await checkPackageManager({ cwd: directory, packageManager })
|
||||
|
||||
if (recipe.preInit) {
|
||||
console.log('===== running initial command(s) =====')
|
||||
await recipe.preInit({ cwd: directory, cfg, packageManager })
|
||||
logStep('Running initial command(s)')
|
||||
await recipe.preInit({
|
||||
cwd: directory,
|
||||
cfg,
|
||||
packageManager,
|
||||
ci: argv.ci,
|
||||
answers: recipeAnswers
|
||||
})
|
||||
}
|
||||
|
||||
const initArgs = [
|
||||
@ -288,7 +277,7 @@ const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
|
||||
// Vue CLI plugin automatically runs these
|
||||
if (recipe.shortName !== 'vuecli') {
|
||||
console.log('===== installing any additional needed deps =====')
|
||||
logStep('Installing any additional needed dependencies')
|
||||
if (argv.dev) {
|
||||
await shell('yarn', ['link', '@tauri-apps/cli'], {
|
||||
cwd: appDirectory
|
||||
@ -307,7 +296,7 @@ const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
packageManager
|
||||
})
|
||||
|
||||
console.log('===== running tauri init =====')
|
||||
logStep(`Running: ${reset(yellow('tauri init'))}`)
|
||||
addTauriScript(appDirectory)
|
||||
|
||||
const binary = !argv.b ? packageManager : resolve(appDirectory, argv.b)
|
||||
@ -321,11 +310,18 @@ const runInit = async (argv: Argv, config: Responses): Promise<void> => {
|
||||
}
|
||||
|
||||
if (recipe.postInit) {
|
||||
console.log('===== running final command(s) =====')
|
||||
logStep('Running final command(s)')
|
||||
await recipe.postInit({
|
||||
cwd: appDirectory,
|
||||
cfg,
|
||||
packageManager
|
||||
packageManager,
|
||||
ci: argv.ci,
|
||||
answers: recipeAnswers
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function logStep(msg: string): void {
|
||||
const out = `${green('>>')} ${bold(cyan(msg))}`
|
||||
console.log(out)
|
||||
}
|
||||
|
@ -2,11 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Recipe } from '..'
|
||||
import { join } from 'path'
|
||||
// @ts-expect-error
|
||||
import scaffe from 'scaffe'
|
||||
import { shell } from '../shell'
|
||||
import { Recipe } from '../types/recipe'
|
||||
|
||||
const afterCra = async (
|
||||
cwd: string,
|
||||
|
@ -2,10 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Recipe } from '..'
|
||||
import { join } from 'path'
|
||||
// @ts-expect-error
|
||||
import scaffe from 'scaffe'
|
||||
import { Recipe } from '../types/recipe'
|
||||
|
||||
export const vanillajs: Recipe = {
|
||||
descriptiveName: 'Vanilla.js',
|
||||
|
@ -2,13 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Recipe } from '..'
|
||||
import { join } from 'path'
|
||||
import { readdirSync } from 'fs'
|
||||
// @ts-expect-error
|
||||
import scaffe from 'scaffe'
|
||||
import { shell } from '../shell'
|
||||
import inquirer from 'inquirer'
|
||||
import { Recipe } from '../types/recipe'
|
||||
|
||||
const afterViteCA = async (
|
||||
cwd: string,
|
||||
@ -41,56 +40,50 @@ const vite: Recipe = {
|
||||
}),
|
||||
extraNpmDevDependencies: [],
|
||||
extraNpmDependencies: [],
|
||||
preInit: async ({ cwd, cfg, packageManager }) => {
|
||||
try {
|
||||
const { template } = (await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'template',
|
||||
message: 'Which vite template would you like to use?',
|
||||
choices: readdirSync(join(__dirname, '../src/templates/vite')),
|
||||
default: 'vue'
|
||||
}
|
||||
])) as { template: string }
|
||||
|
||||
// Vite creates the folder for you
|
||||
if (packageManager === 'yarn') {
|
||||
await shell(
|
||||
'yarn',
|
||||
[
|
||||
'create',
|
||||
'@vitejs/app',
|
||||
`${cfg.appName}`,
|
||||
'--template',
|
||||
`${template}`
|
||||
],
|
||||
{
|
||||
cwd
|
||||
}
|
||||
)
|
||||
} else {
|
||||
await shell(
|
||||
'npx',
|
||||
['@vitejs/create-app', `${cfg.appName}`, '--template', `${template}`],
|
||||
{
|
||||
cwd
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
await afterViteCA(cwd, cfg.appName, template)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
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.'
|
||||
)
|
||||
} else {
|
||||
// Something else went wrong
|
||||
console.error('An unknown error occurred:', error)
|
||||
extraQuestions: ({ ci }) => {
|
||||
return [
|
||||
{
|
||||
type: 'list',
|
||||
name: 'template',
|
||||
message: 'Which vite template would you like to use?',
|
||||
choices: readdirSync(join(__dirname, '../src/templates/vite')),
|
||||
default: 'vue',
|
||||
when: !ci
|
||||
}
|
||||
]
|
||||
},
|
||||
preInit: async ({ cwd, cfg, packageManager, answers }) => {
|
||||
let template = 'vue'
|
||||
if (answers) {
|
||||
template = answers.template ? (answers.template as string) : 'vue'
|
||||
}
|
||||
|
||||
// Vite creates the folder for you
|
||||
if (packageManager === 'yarn') {
|
||||
await shell(
|
||||
'yarn',
|
||||
[
|
||||
'create',
|
||||
'@vitejs/app',
|
||||
`${cfg.appName}`,
|
||||
'--template',
|
||||
`${template}`
|
||||
],
|
||||
{
|
||||
cwd
|
||||
}
|
||||
)
|
||||
} else {
|
||||
await shell(
|
||||
'npx',
|
||||
['@vitejs/create-app', `${cfg.appName}`, '--template', `${template}`],
|
||||
{
|
||||
cwd
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
await afterViteCA(cwd, cfg.appName, template)
|
||||
},
|
||||
postInit: async ({ packageManager }) => {
|
||||
console.log(`
|
||||
|
@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Recipe } from '..'
|
||||
import { join } from 'path'
|
||||
import { shell } from '../shell'
|
||||
import { Recipe } from '../types/recipe'
|
||||
|
||||
const completeLogMsg = `
|
||||
Your installation completed.
|
||||
@ -17,9 +17,20 @@ const vuecli: Recipe = {
|
||||
extraNpmDevDependencies: [],
|
||||
extraNpmDependencies: [],
|
||||
configUpdate: ({ cfg }) => cfg,
|
||||
preInit: async ({ cwd, cfg }) => {
|
||||
preInit: async ({ cwd, cfg, ci, packageManager }) => {
|
||||
// Vue CLI creates the folder for you
|
||||
await shell('npx', ['@vue/cli', 'create', `${cfg.appName}`], { cwd })
|
||||
await shell(
|
||||
'npx',
|
||||
[
|
||||
'@vue/cli',
|
||||
'create',
|
||||
`${cfg.appName}`,
|
||||
'--packageManager',
|
||||
packageManager,
|
||||
ci ? '--default' : ''
|
||||
],
|
||||
{ cwd }
|
||||
)
|
||||
await shell(
|
||||
'npx',
|
||||
[
|
||||
|
23
tooling/create-tauri-app/src/types/recipe.ts
Normal file
23
tooling/create-tauri-app/src/types/recipe.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Answers, QuestionCollection } from 'inquirer'
|
||||
import { PackageManager } from '../dependency-manager'
|
||||
import { TauriBuildConfig } from './config'
|
||||
|
||||
export interface RecipeArgs {
|
||||
cwd: string
|
||||
cfg: TauriBuildConfig
|
||||
packageManager: PackageManager
|
||||
ci: boolean
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||
answers?: undefined | void | Answers
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
descriptiveName: string
|
||||
shortName: string
|
||||
configUpdate?: (args: RecipeArgs) => TauriBuildConfig
|
||||
extraNpmDependencies: string[]
|
||||
extraNpmDevDependencies: string[]
|
||||
extraQuestions?: (args: RecipeArgs) => QuestionCollection[]
|
||||
preInit?: (args: RecipeArgs) => Promise<void>
|
||||
postInit?: (args: RecipeArgs) => Promise<void>
|
||||
}
|
@ -102,9 +102,12 @@ describe('CTA', () => {
|
||||
// and then run that test suite instead
|
||||
let opts: string[] = []
|
||||
if (manager === 'npm') {
|
||||
opts = ['run', 'tauri', '--', 'build']
|
||||
opts =
|
||||
recipe == 'vuecli'
|
||||
? ['run', 'tauri:build']
|
||||
: ['run', 'tauri', '--', 'build']
|
||||
} else if (manager === 'yarn') {
|
||||
opts = ['tauri', 'build']
|
||||
opts = recipe == 'vuecli' ? ['tauri:build'] : ['tauri', 'build']
|
||||
}
|
||||
const tauriBuild = await execa(manager, opts, {
|
||||
all: true,
|
||||
@ -148,6 +151,21 @@ describe('CTA', () => {
|
||||
tauri: 'tauri'
|
||||
})
|
||||
)
|
||||
},
|
||||
vite: () => {
|
||||
expect(packageFileOutput['scripts']).toEqual(
|
||||
expect.objectContaining({
|
||||
tauri: 'tauri'
|
||||
})
|
||||
)
|
||||
},
|
||||
vuecli: () => {
|
||||
expect(packageFileOutput['scripts']).toEqual(
|
||||
expect.objectContaining({
|
||||
'tauri:build': expect.anything(),
|
||||
'tauri:serve': expect.anything()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user