feat: support prompts when invoking plugins

also add invoke prompts for eslint & typescript plugins
This commit is contained in:
Evan You
2018-02-02 16:34:51 -05:00
parent 9f25eedf80
commit c1142e21b0
4 changed files with 164 additions and 19 deletions

View File

@@ -0,0 +1,50 @@
// these prompts are used if the plugin is late-installed into an existing
// project and invoked by `vue invoke`.
const chalk = require('chalk')
const { hasGit } = require('@vue/cli-shared-utils')
module.exports = [
{
name: 'config',
type: 'list',
message: `Pick an ESLint config:`,
choices: [
{
name: 'Error prevention only',
value: 'base',
short: 'Basic'
},
{
name: 'Airbnb',
value: 'airbnb',
short: 'Airbnb'
},
{
name: 'Standard',
value: 'standard',
short: 'Standard'
},
{
name: 'Prettier',
value: 'prettier',
short: 'Prettier'
}
]
},
{
name: 'lintOn',
type: 'checkbox',
message: 'Pick additional lint features:',
choices: [
{
name: 'Lint on save',
value: 'save'
},
{
name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),
value: 'commit'
}
]
}
]

View File

@@ -0,0 +1,43 @@
// these prompts are used if the plugin is late-installed into an existing
// project and invoked by `vue invoke`.
const chalk = require('chalk')
const { hasGit } = require('@vue/cli-shared-utils')
module.exports = [
{
name: `classComponent`,
type: `confirm`,
message: `Use class-style component syntax?`
},
process.env.VUE_CLI_EXPERIMENTAL ? {
name: `experimentalCompileTsWithBabel`,
type: `confirm`,
message: `Compile TS with babel? ${chalk.yellow(`(experimental)`)}`
} : {
name: `useTsWithBabel`,
type: `confirm`,
message: `Use Babel alongside TypeScript for auto-detected polyfills?`
},
{
name: `lint`,
type: `confirm`,
message: `Use TSLint?`
},
{
name: `lintOn`,
type: `checkbox`,
when: answers => answers.lint,
message: `Pick lint features:`,
choices: [
{
name: 'Lint on save',
value: 'save'
},
{
name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),
value: 'commit'
}
]
}
]

View File

@@ -1,8 +1,12 @@
require('../lib/invoke') // so that jest registers the file for this test
jest.setTimeout(10000)
jest.mock('inquirer')
const invoke = require('../lib/invoke')
const { expectPrompts } = require('inquirer')
const create = require('@vue/cli-test-utils/createTestProject')
test('invoke single generator', async () => {
const project = await create('invoke', {
async function createAndInstall (name) {
const project = await create(name, {
plugins: {
'@vue/cli-plugin-babel': {}
}
@@ -11,10 +15,10 @@ test('invoke single generator', async () => {
const pkg = JSON.parse(await project.read('package.json'))
pkg.devDependencies['@vue/cli-plugin-eslint'] = '*'
await project.write('package.json', JSON.stringify(pkg, null, 2))
return project
}
const cliBinPath = require.resolve('../bin/vue')
await project.run(`${cliBinPath} invoke eslint --config airbnb --lintOn save,commit`)
async function assertUpdates (project) {
const updatedPkg = JSON.parse(await project.read('package.json'))
expect(updatedPkg.scripts.lint).toBe('vue-cli-service lint')
expect(updatedPkg.devDependencies).toHaveProperty('lint-staged')
@@ -27,4 +31,30 @@ test('invoke single generator', async () => {
const lintedMain = await project.read('src/main.js')
expect(lintedMain).toMatch(';') // should've been linted in post-generate hook
}
test('invoke with inline options', async () => {
const project = await createAndInstall(`invoke-inline`)
await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb --lintOn save,commit`)
await assertUpdates(project)
})
test('invoke with prompts', async () => {
const project = await createAndInstall(`invoke-prompts`)
expectPrompts([
{
message: `Pick an ESLint config`,
choices: [`Error prevention only`, `Airbnb`, `Standard`, `Prettier`],
choose: 1
},
{
message: `Pick additional lint features`,
choices: [`on save`, 'on commit'],
check: [0, 1]
}
])
// need to be in the same process to have inquirer mocked
// so calling directly
await invoke(`eslint`, {}, project.dir)
await assertUpdates(project)
})

View File

@@ -3,6 +3,7 @@ const path = require('path')
const execa = require('execa')
const chalk = require('chalk')
const resolve = require('resolve')
const inquirer = require('inquirer')
const Generator = require('./Generator')
const { loadOptions } = require('./options')
const installDeps = require('./util/installDeps')
@@ -15,15 +16,23 @@ const {
stopSpinner
} = require('@vue/cli-shared-utils')
async function invoke (pluginName, options) {
function load (request, context) {
let resolvedPath
try {
resolvedPath = resolve.sync(request, { basedir: context })
} catch (e) {}
if (resolvedPath) {
return require(resolvedPath)
}
}
async function invoke (pluginName, options = {}, context = process.cwd()) {
delete options._
const context = process.cwd()
const pkgPath = path.resolve(context, 'package.json')
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
if (!fs.existsSync(pkgPath)) {
error(`package.json not found in ${chalk.yellow(context)}`)
process.exit(1)
throw new Error(`package.json not found in ${chalk.yellow(context)}`)
}
const pkg = require(pkgPath)
@@ -41,18 +50,29 @@ async function invoke (pluginName, options) {
const id = findPlugin(pkg.devDependencies) || findPlugin(pkg.dependencies)
if (!id) {
error(
throw new Error(
`Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` +
`Did you forget to install it?`
)
process.exit(1)
}
const generatorURI = `${id}/generator`
const generatorPath = resolve.sync(generatorURI, { basedir: context })
const pluginGenerator = load(`${id}/generator`, context)
if (!pluginGenerator) {
throw new Error(`Plugin ${id} does not have a generator.`)
}
// resolve options if no command line options are passed, and the plugin
// contains a prompt module.
if (!Object.keys(options).length) {
const pluginPrompts = load(`${id}/prompts`, context)
if (pluginPrompts) {
options = await inquirer.prompt(pluginPrompts)
}
}
const plugin = {
id,
apply: require(generatorPath),
apply: pluginGenerator,
options
}
@@ -93,7 +113,7 @@ async function invoke (pluginName, options) {
log()
log(` Successfully invoked generator for plugin: ${chalk.cyan(id)}`)
if (hasGit()) {
if (!process.env.VUE_CLI_TEST && hasGit()) {
const { stdout } = await execa('git', ['ls-files', '--exclude-standard', '--modified', '--others'])
log(` The following files have been updated / added:\n`)
log(chalk.red(stdout.split(/\r?\n/g).map(line => ` ${line}`).join('\n')))
@@ -103,9 +123,11 @@ async function invoke (pluginName, options) {
log()
}
module.exports = (pluginName, options) => {
invoke(pluginName, options).catch(err => {
module.exports = (...args) => {
return invoke(...args).catch(err => {
error(err)
process.exit(1)
if (!process.env.VUE_CLI_TEST) {
process.exit(1)
}
})
}