Skip to content

Commit c1142e2

Browse files
committed
feat: support prompts when invoking plugins
also add invoke prompts for eslint & typescript plugins
1 parent 9f25eed commit c1142e2

File tree

4 files changed

+164
-19
lines changed

4 files changed

+164
-19
lines changed
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// these prompts are used if the plugin is late-installed into an existing
2+
// project and invoked by `vue invoke`.
3+
4+
const chalk = require('chalk')
5+
const { hasGit } = require('@vue/cli-shared-utils')
6+
7+
module.exports = [
8+
{
9+
name: 'config',
10+
type: 'list',
11+
message: `Pick an ESLint config:`,
12+
choices: [
13+
{
14+
name: 'Error prevention only',
15+
value: 'base',
16+
short: 'Basic'
17+
},
18+
{
19+
name: 'Airbnb',
20+
value: 'airbnb',
21+
short: 'Airbnb'
22+
},
23+
{
24+
name: 'Standard',
25+
value: 'standard',
26+
short: 'Standard'
27+
},
28+
{
29+
name: 'Prettier',
30+
value: 'prettier',
31+
short: 'Prettier'
32+
}
33+
]
34+
},
35+
{
36+
name: 'lintOn',
37+
type: 'checkbox',
38+
message: 'Pick additional lint features:',
39+
choices: [
40+
{
41+
name: 'Lint on save',
42+
value: 'save'
43+
},
44+
{
45+
name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),
46+
value: 'commit'
47+
}
48+
]
49+
}
50+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// these prompts are used if the plugin is late-installed into an existing
2+
// project and invoked by `vue invoke`.
3+
4+
const chalk = require('chalk')
5+
const { hasGit } = require('@vue/cli-shared-utils')
6+
7+
module.exports = [
8+
{
9+
name: `classComponent`,
10+
type: `confirm`,
11+
message: `Use class-style component syntax?`
12+
},
13+
process.env.VUE_CLI_EXPERIMENTAL ? {
14+
name: `experimentalCompileTsWithBabel`,
15+
type: `confirm`,
16+
message: `Compile TS with babel? ${chalk.yellow(`(experimental)`)}`
17+
} : {
18+
name: `useTsWithBabel`,
19+
type: `confirm`,
20+
message: `Use Babel alongside TypeScript for auto-detected polyfills?`
21+
},
22+
{
23+
name: `lint`,
24+
type: `confirm`,
25+
message: `Use TSLint?`
26+
},
27+
{
28+
name: `lintOn`,
29+
type: `checkbox`,
30+
when: answers => answers.lint,
31+
message: `Pick lint features:`,
32+
choices: [
33+
{
34+
name: 'Lint on save',
35+
value: 'save'
36+
},
37+
{
38+
name: 'Lint and fix on commit' + (hasGit() ? '' : chalk.red(' (requires Git)')),
39+
value: 'commit'
40+
}
41+
]
42+
}
43+
]
+36-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
require('../lib/invoke') // so that jest registers the file for this test
1+
jest.setTimeout(10000)
2+
jest.mock('inquirer')
3+
4+
const invoke = require('../lib/invoke')
5+
const { expectPrompts } = require('inquirer')
26
const create = require('@vue/cli-test-utils/createTestProject')
37

4-
test('invoke single generator', async () => {
5-
const project = await create('invoke', {
8+
async function createAndInstall (name) {
9+
const project = await create(name, {
610
plugins: {
711
'@vue/cli-plugin-babel': {}
812
}
@@ -11,10 +15,10 @@ test('invoke single generator', async () => {
1115
const pkg = JSON.parse(await project.read('package.json'))
1216
pkg.devDependencies['@vue/cli-plugin-eslint'] = '*'
1317
await project.write('package.json', JSON.stringify(pkg, null, 2))
18+
return project
19+
}
1420

15-
const cliBinPath = require.resolve('../bin/vue')
16-
await project.run(`${cliBinPath} invoke eslint --config airbnb --lintOn save,commit`)
17-
21+
async function assertUpdates (project) {
1822
const updatedPkg = JSON.parse(await project.read('package.json'))
1923
expect(updatedPkg.scripts.lint).toBe('vue-cli-service lint')
2024
expect(updatedPkg.devDependencies).toHaveProperty('lint-staged')
@@ -27,4 +31,30 @@ test('invoke single generator', async () => {
2731

2832
const lintedMain = await project.read('src/main.js')
2933
expect(lintedMain).toMatch(';') // should've been linted in post-generate hook
34+
}
35+
36+
test('invoke with inline options', async () => {
37+
const project = await createAndInstall(`invoke-inline`)
38+
await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb --lintOn save,commit`)
39+
await assertUpdates(project)
40+
})
41+
42+
test('invoke with prompts', async () => {
43+
const project = await createAndInstall(`invoke-prompts`)
44+
expectPrompts([
45+
{
46+
message: `Pick an ESLint config`,
47+
choices: [`Error prevention only`, `Airbnb`, `Standard`, `Prettier`],
48+
choose: 1
49+
},
50+
{
51+
message: `Pick additional lint features`,
52+
choices: [`on save`, 'on commit'],
53+
check: [0, 1]
54+
}
55+
])
56+
// need to be in the same process to have inquirer mocked
57+
// so calling directly
58+
await invoke(`eslint`, {}, project.dir)
59+
await assertUpdates(project)
3060
})

packages/@vue/cli/lib/invoke.js

+35-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const path = require('path')
33
const execa = require('execa')
44
const chalk = require('chalk')
55
const resolve = require('resolve')
6+
const inquirer = require('inquirer')
67
const Generator = require('./Generator')
78
const { loadOptions } = require('./options')
89
const installDeps = require('./util/installDeps')
@@ -15,15 +16,23 @@ const {
1516
stopSpinner
1617
} = require('@vue/cli-shared-utils')
1718

18-
async function invoke (pluginName, options) {
19+
function load (request, context) {
20+
let resolvedPath
21+
try {
22+
resolvedPath = resolve.sync(request, { basedir: context })
23+
} catch (e) {}
24+
if (resolvedPath) {
25+
return require(resolvedPath)
26+
}
27+
}
28+
29+
async function invoke (pluginName, options = {}, context = process.cwd()) {
1930
delete options._
20-
const context = process.cwd()
2131
const pkgPath = path.resolve(context, 'package.json')
2232
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
2333

2434
if (!fs.existsSync(pkgPath)) {
25-
error(`package.json not found in ${chalk.yellow(context)}`)
26-
process.exit(1)
35+
throw new Error(`package.json not found in ${chalk.yellow(context)}`)
2736
}
2837

2938
const pkg = require(pkgPath)
@@ -41,18 +50,29 @@ async function invoke (pluginName, options) {
4150

4251
const id = findPlugin(pkg.devDependencies) || findPlugin(pkg.dependencies)
4352
if (!id) {
44-
error(
53+
throw new Error(
4554
`Cannot resolve plugin ${chalk.yellow(pluginName)} from package.json. ` +
4655
`Did you forget to install it?`
4756
)
48-
process.exit(1)
4957
}
5058

51-
const generatorURI = `${id}/generator`
52-
const generatorPath = resolve.sync(generatorURI, { basedir: context })
59+
const pluginGenerator = load(`${id}/generator`, context)
60+
if (!pluginGenerator) {
61+
throw new Error(`Plugin ${id} does not have a generator.`)
62+
}
63+
64+
// resolve options if no command line options are passed, and the plugin
65+
// contains a prompt module.
66+
if (!Object.keys(options).length) {
67+
const pluginPrompts = load(`${id}/prompts`, context)
68+
if (pluginPrompts) {
69+
options = await inquirer.prompt(pluginPrompts)
70+
}
71+
}
72+
5373
const plugin = {
5474
id,
55-
apply: require(generatorPath),
75+
apply: pluginGenerator,
5676
options
5777
}
5878

@@ -93,7 +113,7 @@ async function invoke (pluginName, options) {
93113

94114
log()
95115
log(` Successfully invoked generator for plugin: ${chalk.cyan(id)}`)
96-
if (hasGit()) {
116+
if (!process.env.VUE_CLI_TEST && hasGit()) {
97117
const { stdout } = await execa('git', ['ls-files', '--exclude-standard', '--modified', '--others'])
98118
log(` The following files have been updated / added:\n`)
99119
log(chalk.red(stdout.split(/\r?\n/g).map(line => ` ${line}`).join('\n')))
@@ -103,9 +123,11 @@ async function invoke (pluginName, options) {
103123
log()
104124
}
105125

106-
module.exports = (pluginName, options) => {
107-
invoke(pluginName, options).catch(err => {
126+
module.exports = (...args) => {
127+
return invoke(...args).catch(err => {
108128
error(err)
109-
process.exit(1)
129+
if (!process.env.VUE_CLI_TEST) {
130+
process.exit(1)
131+
}
110132
})
111133
}

0 commit comments

Comments
 (0)