diff --git a/packages/@vue/cli-plugin-unit-mocha-webpack/index.js b/packages/@vue/cli-plugin-unit-mocha-webpack/index.js index 06b1400fd..10e41b39e 100644 --- a/packages/@vue/cli-plugin-unit-mocha-webpack/index.js +++ b/packages/@vue/cli-plugin-unit-mocha-webpack/index.js @@ -4,7 +4,6 @@ module.exports = service => { }) service.configureWebpack({ env: 'test' }, webpackConfig => { - require('jsdom-global')() if (!webpackConfig.externals) { webpackConfig.externals = [] } diff --git a/packages/@vue/cli-plugin-unit-mocha-webpack/runner.js b/packages/@vue/cli-plugin-unit-mocha-webpack/runner.js index e69de29bb..bbd0e636a 100644 --- a/packages/@vue/cli-plugin-unit-mocha-webpack/runner.js +++ b/packages/@vue/cli-plugin-unit-mocha-webpack/runner.js @@ -0,0 +1 @@ +require('jsdom-global')() diff --git a/packages/@vue/cli/bin/vue b/packages/@vue/cli/bin/vue old mode 100644 new mode 100755 diff --git a/packages/@vue/cli/bin/vue-create b/packages/@vue/cli/bin/vue-create old mode 100644 new mode 100755 diff --git a/packages/@vue/cli/bin/vue-init b/packages/@vue/cli/bin/vue-init old mode 100644 new mode 100755 diff --git a/packages/@vue/cli/lib/Creator.js b/packages/@vue/cli/lib/Creator.js index 4aa1f7b0c..0bff6be25 100644 --- a/packages/@vue/cli/lib/Creator.js +++ b/packages/@vue/cli/lib/Creator.js @@ -10,12 +10,19 @@ const writeFileTree = require('./util/writeFileTree') const rcPath = path.join(os.homedir(), '.vuerc') const isMode = _mode => ({ mode }) => _mode === mode +const defaultOptions = { + features: ['eslint', 'unit'], + eslint: 'eslint-only', + unit: 'mocha', + assertionLibrary: 'expect' +} + module.exports = class Creator { constructor (name, generators) { this.name = name - const { modePrompt, featuresPrompt } = this.resolveIntroPrompts() + const { modePrompt, featurePrompt } = this.resolveIntroPrompts() this.modePrompt = modePrompt - this.featuresPrompt = featuresPrompt + this.featurePrompt = featurePrompt this.outroPrompts = this.resolveOutroPrompts() this.injectedPrompts = [] this.deps = {} @@ -27,30 +34,38 @@ module.exports = class Creator { this.promptCompleteCbs = [] this.fileMiddlewares = [] - generators.forEach(({ module }) => { - module(new GeneratorAPI(this, generator)) + generators.forEach(generator => { + generator.module(new GeneratorAPI(this, generator)) }) } async create (path) { - // 1. prompt + // prompt let options = await inquirer.prompt(this.resolveFinalPrompts()) + let needSave = false if (options.mode === 'saved') { options = this.loadSavedOptions() } else if (options.mode === 'default') { - options = this.loadDefaultOptions() + options = defaultOptions } else if (options.save) { - this.saveOptions(options) + needSave = true } options.features = options.features || [] - // 2. run cbs (register generators) - this.promptCompleteCbs.forEach((cb => cb(options)) - // 3. resolve deps, scripts and generate final package.json + // run cbs (register generators) + this.promptCompleteCbs.forEach(cb => cb(options)) + + // save after prompt complete cbs are run, since generators may modify + // options in the callback + if (needSave) { + this.saveOptions(options) + } + + // resolve deps, scripts and generate final package.json this.resolvePackage() - // 4. wait for file resolve + // wait for file resolve await this.resolveFiles() - // 5. write file tree to disk + // write file tree to disk await writeFileTree(path, this.files) } @@ -80,12 +95,12 @@ module.exports = class Creator { name: 'features', when: isMode('manual'), type: 'checkbox', - message: 'Please check all features needed for your project.', + message: 'Please check the features needed for your project.', choices: [] } return { modePrompt, - featuresPrompt + featurePrompt } } @@ -130,6 +145,7 @@ module.exports = class Creator { message: 'Automatically install NPM dependencies after project creation?' }) } + return outroPrompts } resolveFinalPrompts () { @@ -140,12 +156,14 @@ module.exports = class Creator { return options.mode === 'manual' && originalWhen(options) } }) - return [].concat([ + const ret = [].concat( this.modePrompt, - this.featuresPrompt, + this.featurePrompt, this.injectedPrompts, this.outroPrompts - ]) + ) + console.log(ret) + return ret } loadSavedOptions () { @@ -208,9 +226,10 @@ module.exports = class Creator { return this.packageFields } - // TODO async resolveFiles () { - + for (const middleware of this.fileMiddlewares) { + await middleware(this.files) + } } } diff --git a/packages/@vue/cli/lib/GeneratorAPI.js b/packages/@vue/cli/lib/GeneratorAPI.js index 42b96e24a..52af53a1c 100644 --- a/packages/@vue/cli/lib/GeneratorAPI.js +++ b/packages/@vue/cli/lib/GeneratorAPI.js @@ -1,3 +1,5 @@ +const { error } = require('./util/log') + module.exports = class GeneratorAPI { constructor (creator, generator) { this.creator = creator @@ -8,46 +10,50 @@ module.exports = class GeneratorAPI { this.creator.featurePrompt.choices.push(feature) } - injectOptionForFeature (featureName, option) { - const feature = this.creator.featurePrompt.choices.find(f => { - return f.name === featureName - }) - if (!feature) { - throw new Error( - `injectOptionForFeature error in generator "${ - this.generator.id - }": feature "${featureName}" does not exist.` - ) - } - feature.choices.push(option) - } - injectPrompt (prompt) { this.creator.injectedPrompts.push(prompt) } + injectOptionForPrompt (name, option) { + const prompt = this.creator.injectedPrompts.find(f => { + return f.name === name + }) + if (!prompt) { + error( + `injectOptionForFeature error in generator "${ + this.generator.id + }": prompt "${name}" does not exist.` + ) + } + prompt.choices.push(option) + } + onPromptComplete (cb) { this.creator.promptCompleteCbs.push(cb) } injectDeps (deps) { + Object.assign(this.creator.deps, deps) + } + injectDevDeps(deps) { + Object.assign(this.creator.devDeps, deps) } injectScripts (scripts) { - + Object.assign(this.creator.scripts, scripts) } injectPackageFields (fields) { - + Object.assign(this.creator.packageFields, fields) } - injectFilesMiddleware (middleware) { + injectFileMiddleware (middleware) { this.creator.fileMiddlewares.push(middleware) } renderFile (file) { - + return file } onCreateComplete (msg) { diff --git a/packages/@vue/cli/lib/create.js b/packages/@vue/cli/lib/create.js index f567a13bf..51ac83aa8 100644 --- a/packages/@vue/cli/lib/create.js +++ b/packages/@vue/cli/lib/create.js @@ -1,36 +1,32 @@ +const fs = require('fs') const path = require('path') const program = require('commander') const Creator = require('./Creator') const Generator = require('./Generator') -const { info, error } = require('./util/log') +const { warn, error } = require('./util/log') const resolveInstalledGenerators = require('./util/resolveInstalledGenerators') -program.usage('') +program + .usage('') + .parse(process.argv) -program.on('help', () => { - console.log('TODO') -}) +const projectName = program.args[0] +if (!projectName) { + warn(`\n Please provide an app name.`) + program.outputHelp() + process.exit(1) +} -program.parse(process.argv) - -const builtInGenerators = [ - 'core', - 'babel', - 'typescript', - 'pwa', - 'eslint', - 'unit-jest', - 'unit-mocha-webpack', - 'e2e-nightwatch', - 'e2e-cypress' -].map(id => new Generator(id, `./generators/${id}`)) +const builtInGenerators = fs + .readdirSync(path.resolve(__dirname, './generators')) + .map(id => new Generator(id, `./generators/${id}`)) const installedGenerators = resolveInstalledGenerators().map(id => { return new Generator(id) }) -const creator = new Creator(builtInGenerators.concat(installedGenerators)) -const targetDir = path.resolve(process.cwd(), program.args[0]) +const targetDir = path.resolve(process.cwd(), projectName) +const creator = new Creator(projectName, builtInGenerators.concat(installedGenerators)) creator .create(targetDir) diff --git a/packages/@vue/cli/lib/generators/babel/index.js b/packages/@vue/cli/lib/generators/babel/index.js index 676018952..36e6e5c16 100644 --- a/packages/@vue/cli/lib/generators/babel/index.js +++ b/packages/@vue/cli/lib/generators/babel/index.js @@ -1,12 +1,12 @@ module.exports = api => { api.onPromptComplete(options => { if (!options.features.includes('ts')) { - api.injectDeps({ + api.injectDevDeps({ '@vue/cli-plugin-babel': '^1.0.0', 'babel-preset-vue-app': '^2.0.0' }) api.injectFileMiddleware(files => { - files['.babelrc'] = api.renderFile('./files/.babelrc') + files['.babelrc'] = api.renderFile('.babelrc') }) } }) diff --git a/packages/@vue/cli/lib/generators/core/index.js b/packages/@vue/cli/lib/generators/core/index.js index e69de29bb..61406aa61 100644 --- a/packages/@vue/cli/lib/generators/core/index.js +++ b/packages/@vue/cli/lib/generators/core/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/generators/e2e/index.js b/packages/@vue/cli/lib/generators/e2e/index.js new file mode 100644 index 000000000..61406aa61 --- /dev/null +++ b/packages/@vue/cli/lib/generators/e2e/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/generators/eslint/index.js b/packages/@vue/cli/lib/generators/eslint/index.js new file mode 100644 index 000000000..61406aa61 --- /dev/null +++ b/packages/@vue/cli/lib/generators/eslint/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/generators/pwa/index.js b/packages/@vue/cli/lib/generators/pwa/index.js new file mode 100644 index 000000000..61406aa61 --- /dev/null +++ b/packages/@vue/cli/lib/generators/pwa/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/generators/router/index.js b/packages/@vue/cli/lib/generators/router/index.js new file mode 100644 index 000000000..61406aa61 --- /dev/null +++ b/packages/@vue/cli/lib/generators/router/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/generators/typescript/index.js b/packages/@vue/cli/lib/generators/typescript/index.js new file mode 100644 index 000000000..61406aa61 --- /dev/null +++ b/packages/@vue/cli/lib/generators/typescript/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/generators/unit-mocha-webpack/index.js b/packages/@vue/cli/lib/generators/unit-mocha-webpack/index.js deleted file mode 100644 index f94533b3f..000000000 --- a/packages/@vue/cli/lib/generators/unit-mocha-webpack/index.js +++ /dev/null @@ -1,55 +0,0 @@ -module.exports = api => { - api.injectOptionForFeature('unit', { - name: 'Mocha (with better webpack integration, https://mochajs.org/)', - value: 'mocha', - short: 'Mocha' - }) - - api.injectPrompt({ - name: 'mochaAssertionLibrary', - message: 'Pick an assertion library for unit tests:', - when: options => options.unit === 'mocha', - type: 'list', - choices: [ - { - name: 'Chai (http://chaijs.com/)', - value: 'chai', - short: 'Chai' - }, - { - name: 'Expect (https://facebook.github.io/jest/docs/en/expect.html)', - value: 'expect', - short: 'Expect' - }, - { - name: `I'll pick my own`, - value: 'custom', - short: 'Custom' - } - ] - }) - - api.onPromptComplete(options => { - if (options.unit === 'mocha') { - const dependencies = { - '@vue/api-plugin-unit-mocha-webpack': '^1.0.0' - 'vue-test-utils': '^1.0.0' - } - if (options.mochaAssertionLibrary === 'chai') { - dependencies.chai = '*' - } else if (options.mochaAssertionLibrary === 'expect') { - dependencies.expect = '*' - } - api.injectDependencies(dependencies) - - api.injectScripts({ - test: 'vue-api-service test' - }) - - api.injectFileMiddleware(files => { - // add dummy test - files['test/unit/Hello.spec.js'] = api.renderFile('./files/Hello.spec.js') - }) - } - }) -} diff --git a/packages/@vue/cli/lib/generators/unit-mocha-webpack/files/Hello.spec.js b/packages/@vue/cli/lib/generators/unit/files/Hello.spec.js similarity index 60% rename from packages/@vue/cli/lib/generators/unit-mocha-webpack/files/Hello.spec.js rename to packages/@vue/cli/lib/generators/unit/files/Hello.spec.js index e5386e989..06df0b74f 100644 --- a/packages/@vue/cli/lib/generators/unit-mocha-webpack/files/Hello.spec.js +++ b/packages/@vue/cli/lib/generators/unit/files/Hello.spec.js @@ -1,11 +1,11 @@ import { shallow } from 'vue-test-utils' import Hello from '@/components/Hello.vue' -{{#if_eq mochaAssertionLibrary 'expect'}} +<% if (assertionLibrary === 'expect') { %> import { expect } from 'expect' -{{/if_eq}} -{{#if_eq mochaAssertionLibrary 'chai'}} +<% } %> +<% if (assertionLibrary === 'chai') { %> import { expect } from 'chai' -{{/if_eq}} +<% } %> describe('Hello.vue', () => { it('renders props.msg when passed', () => { @@ -13,14 +13,14 @@ describe('Hello.vue', () => { const wrapper = shallow(Hello, { context: { props: { msg } } }) - {{#if_eq mochaAssertionLibrary 'expect'}} + <% if (assertionLibrary === 'expect' || unit === 'jest') { %> expect(wrapper.text()).toBe(msg) - {{/if_eq}} - {{#if_eq mochaAssertionLibrary 'chai'}} + <% } %> + <% if (assertionLibrary === 'chai') { %> expect(wrapper.text()).to.equal(msg) - {{/if_eq}} - {{#if_eq mochaAssertionLibrary 'custom'}} + <% } %> + <% if (assertionLibrary === 'custom') { %> // assert wrapper.text() equals msg - {{/if_eq}} + <% } %> }) }) diff --git a/packages/@vue/cli/lib/generators/unit/index.js b/packages/@vue/cli/lib/generators/unit/index.js new file mode 100644 index 000000000..baf25ece5 --- /dev/null +++ b/packages/@vue/cli/lib/generators/unit/index.js @@ -0,0 +1,58 @@ +module.exports = api => { + api.injectFeature({ + name: 'Unit Testing', + value: 'unit', + short: 'Unit' + }) + + api.injectPrompt({ + name: 'unit', + when: options => options.features.includes('unit'), + type: 'list', + message: 'Pick a unit testing solution:', + choices: [ + { + name: 'Mocha (with better webpack integration, https://mochajs.org/)', + value: 'mocha', + short: 'Mocha' + }, + { + name: 'Jest (https://facebook.github.io/jest/)', + value: 'jest', + short: 'Jest' + } + ] + }) + + api.injectPrompt({ + name: 'assertionLibrary', + message: 'Pick an assertion library for unit tests:', + when: options => options.unit === 'mocha', + type: 'list', + choices: [ + { + name: 'Chai (http://chaijs.com/)', + value: 'chai', + short: 'Chai' + }, + { + name: 'Expect (https://facebook.github.io/jest/docs/en/expect.html)', + value: 'expect', + short: 'Expect' + }, + { + name: `I'll pick my own`, + value: 'custom', + short: 'Custom' + } + ] + }) + + api.onPromptComplete(options => { + if (options.unit === 'mocha') { + require('./mocha-webpack')(api, options) + } else if (options.unit === 'jest') { + require('./jest')(api, options) + } + }) +} diff --git a/packages/@vue/cli/lib/generators/unit/jest.js b/packages/@vue/cli/lib/generators/unit/jest.js new file mode 100644 index 000000000..930c08ec9 --- /dev/null +++ b/packages/@vue/cli/lib/generators/unit/jest.js @@ -0,0 +1,42 @@ +module.exports = (api, options) => { + api.injectDevDeps({ + '@vue/cli-plugin-unit-jest': '^1.0.0', + "jest": "^22.0.4", + 'vue-test-utils': '^1.0.0' + }) + + api.injectScripts({ + test: 'jest' + }) + + api.injectPackageFields({ + "jest": { + "moduleFileExtensions": [ + "js", + "json", + // tell Jest to handle *.vue files + "vue" + ], + "transform": { + // process js with babel-jest + "^.+\\.js$": "/node_modules/@vue/cli-plugin-unit-jest/node_modules/babel-jest", + // process *.vue files with vue-jest + ".*\\.(vue)$": "/node_modules/@vue/cli-plugin-unit-jest/node_modules/vue-jest" + }, + // support the same @ -> src alias mapping in source code + "moduleNameMapper": { + "^@/(.*)$": "/src/$1" + }, + // serializer for snapshots + "snapshotSerializers": [ + "/node_modules/@vue/cli-plugin-unit-jest/node_modules/jest-serializer-vue" + ], + "mapCoverage": true + } + }) + + api.injectFileMiddleware(files => { + // add dummy test + files['test/unit/Hello.spec.js'] = api.renderFile('Hello.spec.js') + }) +} diff --git a/packages/@vue/cli/lib/generators/unit/mocha-webpack.js b/packages/@vue/cli/lib/generators/unit/mocha-webpack.js new file mode 100644 index 000000000..82328f67e --- /dev/null +++ b/packages/@vue/cli/lib/generators/unit/mocha-webpack.js @@ -0,0 +1,21 @@ +module.exports = (api, options) => { + const dependencies = { + '@vue/cli-plugin-unit-mocha-webpack': '^1.0.0', + 'vue-test-utils': '^1.0.0' + } + if (options.assertionLibrary === 'chai') { + dependencies.chai = '^4.1.2' + } else if (options.assertionLibrary === 'expect') { + dependencies.expect = '^22.0.3' + } + api.injectDevDeps(dependencies) + + api.injectScripts({ + test: 'vue-cli-service test' + }) + + api.injectFileMiddleware(files => { + // add dummy test + files['test/unit/Hello.spec.js'] = api.renderFile('Hello.spec.js') + }) +} diff --git a/packages/@vue/cli/lib/generators/vuex/index.js b/packages/@vue/cli/lib/generators/vuex/index.js new file mode 100644 index 000000000..61406aa61 --- /dev/null +++ b/packages/@vue/cli/lib/generators/vuex/index.js @@ -0,0 +1,3 @@ +module.exports = api => { + +} diff --git a/packages/@vue/cli/lib/util/log.js b/packages/@vue/cli/lib/util/log.js index 551ee385e..8d9448792 100644 --- a/packages/@vue/cli/lib/util/log.js +++ b/packages/@vue/cli/lib/util/log.js @@ -1,15 +1,21 @@ +const chalk = require('chalk') + exports.info = msg => { - console.log(msg) + console.log(chalk.gray(`\n ${msg}\n`)) } exports.success = msg => { - console.log(msg) + console.log(chalk.green(`\n ${msg}\n`)) } exports.warn = msg => { - console.warn(msg) + console.warn(chalk.yellow(`\n ${msg}\n`)) } exports.error = msg => { - console.error(msg) + console.error(`\n ${chalk.bgRed(' ERROR ')} ${chalk.red(msg)}\n`) + if (msg instanceof Error) { + console.log(msg.stack) + } + process.exit(1) } diff --git a/packages/@vue/cli/test.js b/packages/@vue/cli/test.js index b221367ab..dabdd5c60 100644 --- a/packages/@vue/cli/test.js +++ b/packages/@vue/cli/test.js @@ -45,7 +45,8 @@ inquirer.prompt([ }, { name: 'Linter', - value: 'linter' + value: 'linter', + short: 'Linter' }, { name: 'Unit Testing', @@ -66,7 +67,7 @@ inquirer.prompt([ message: 'Pick a linter solution:', choices: [ { - name: 'ESLint w/ only error-prevention rules & configure the rest myself', + name: 'ESLint w/ only error-prevention rules', value: 'eslint-only', short: 'ESLint only' },