Skip to content

Commit

Permalink
feat: support prompts when invoking plugins
Browse files Browse the repository at this point in the history
also add invoke prompts for eslint & typescript plugins
  • Loading branch information
yyx990803 committed Feb 2, 2018
1 parent 9f25eed commit c1142e2
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 19 deletions.
50 changes: 50 additions & 0 deletions packages/@vue/cli-plugin-eslint/prompts.js
Original file line number Diff line number Diff line change
@@ -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'
}
]
}
]
43 changes: 43 additions & 0 deletions packages/@vue/cli-plugin-typescript/prompts.js
Original file line number Diff line number Diff line change
@@ -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'
}
]
}
]
42 changes: 36 additions & 6 deletions packages/@vue/cli/__tests__/invoke.spec.js
Original file line number Diff line number Diff line change
@@ -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': {}
}
Expand All @@ -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')
Expand All @@ -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)
})
48 changes: 35 additions & 13 deletions packages/@vue/cli/lib/invoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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')))
Expand All @@ -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)
}
})
}

0 comments on commit c1142e2

Please sign in to comment.