diff --git a/packages/@vue/cli-plugin-eslint/prompts.js b/packages/@vue/cli-plugin-eslint/prompts.js new file mode 100644 index 0000000000..e292129288 --- /dev/null +++ b/packages/@vue/cli-plugin-eslint/prompts.js @@ -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' + } + ] + } +] diff --git a/packages/@vue/cli-plugin-typescript/prompts.js b/packages/@vue/cli-plugin-typescript/prompts.js new file mode 100644 index 0000000000..0bb3747b4a --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/prompts.js @@ -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' + } + ] + } +] diff --git a/packages/@vue/cli/__tests__/invoke.spec.js b/packages/@vue/cli/__tests__/invoke.spec.js index d0bbd65c75..1b95447ff4 100644 --- a/packages/@vue/cli/__tests__/invoke.spec.js +++ b/packages/@vue/cli/__tests__/invoke.spec.js @@ -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) }) diff --git a/packages/@vue/cli/lib/invoke.js b/packages/@vue/cli/lib/invoke.js index 6d7e5340c1..1b79cf0fa2 100644 --- a/packages/@vue/cli/lib/invoke.js +++ b/packages/@vue/cli/lib/invoke.js @@ -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) + } }) }