mirror of https://github.com/tauri-apps/tauri
chore: shift CTA from bin to .ts (#1651)
* chore: shift CTA from bin to .ts * add change file * fix rollup build
This commit is contained in:
parent
252014cae2
commit
c3acbd68ec
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"create-tauri-app": patch
|
||||
---
|
||||
|
||||
Shift everything out of the `bin` and into `.ts` so we can apply Typescript types.
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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'
|
||||
|
||||
export { shell } from './shell'
|
||||
export { install, checkPackageManager } from './dependency-manager'
|
||||
export { addTauriScript } from './helpers/add-tauri-script'
|
||||
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
|
||||
}
|
||||
|
||||
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]
|
||||
const allRecipes: Recipe[] = [vanillajs, reactjs, reactts, vite, vuecli]
|
||||
|
||||
export const recipeNames: Array<[string, string]> = allRecipes.map((r) => [
|
||||
r.shortName,
|
||||
r.descriptiveName
|
||||
])
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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__"]
|
||||
|
|
Loading…
Reference in New Issue