This commit is contained in:
Evan You
2017-12-24 18:16:12 -05:00
parent b2d6f27438
commit 763dd35fa0
23 changed files with 246 additions and 131 deletions

View File

@@ -4,7 +4,6 @@ module.exports = service => {
})
service.configureWebpack({ env: 'test' }, webpackConfig => {
require('jsdom-global')()
if (!webpackConfig.externals) {
webpackConfig.externals = []
}

View File

@@ -0,0 +1 @@
require('jsdom-global')()

0
packages/@vue/cli/bin/vue Normal file → Executable file
View File

0
packages/@vue/cli/bin/vue-create Normal file → Executable file
View File

0
packages/@vue/cli/bin/vue-init Normal file → Executable file
View File

View File

@@ -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)
}
}
}

View File

@@ -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) {

View File

@@ -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('<app-name>')
program
.usage('<app-name>')
.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)

View File

@@ -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')
})
}
})

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -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')
})
}
})
}

View File

@@ -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}}
<% } %>
})
})

View File

@@ -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)
}
})
}

View File

@@ -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$": "<rootDir>/node_modules/@vue/cli-plugin-unit-jest/node_modules/babel-jest",
// process *.vue files with vue-jest
".*\\.(vue)$": "<rootDir>/node_modules/@vue/cli-plugin-unit-jest/node_modules/vue-jest"
},
// support the same @ -> src alias mapping in source code
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1"
},
// serializer for snapshots
"snapshotSerializers": [
"<rootDir>/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')
})
}

View File

@@ -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')
})
}

View File

@@ -0,0 +1,3 @@
module.exports = api => {
}

View File

@@ -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)
}

View File

@@ -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'
},