Skip to content

Commit

Permalink
fix: handle vue invoke config merging for existing files
Browse files Browse the repository at this point in the history
close #788
  • Loading branch information
yyx990803 committed Feb 6, 2018
1 parent 1902d9c commit 46166fb
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 107 deletions.
8 changes: 7 additions & 1 deletion packages/@vue/cli-test-utils/createTestProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const execa = require('execa')
const { promisify } = require('util')
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const rmFile = promisify(fs.unlink)
const mkdirp = promisify(require('mkdirp'))

module.exports = function createTestProject (name, preset, cwd) {
Expand All @@ -25,6 +26,10 @@ module.exports = function createTestProject (name, preset, cwd) {
return mkdirp(dir).then(() => writeFile(targetPath, content))
}

const rm = file => {
return rmFile(path.resolve(projectRoot, file))
}

const run = (command, args) => {
[command, ...args] = command.split(/\s+/)
if (command === 'vue-cli-service') {
Expand Down Expand Up @@ -54,6 +59,7 @@ module.exports = function createTestProject (name, preset, cwd) {
has,
read,
write,
run
run,
rm
}))
}
43 changes: 5 additions & 38 deletions packages/@vue/cli/__tests__/Generator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,41 +67,6 @@ test('api: extendPackage function', async () => {
})
})

test('api: extendPackage + { merge: false }', async () => {
const generator = new Generator('/', {
name: 'hello',
list: [1],
vue: {
foo: 1,
bar: 2
}
}, [{
id: 'test',
apply: api => {
api.extendPackage({
name: 'hello2',
list: [2],
vue: {
foo: 2,
baz: 3
}
}, { merge: false })
}
}])

await generator.generate()

const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
expect(pkg).toEqual({
name: 'hello2',
list: [2],
vue: {
foo: 2,
baz: 3
}
})
})

test('api: extendPackage merge dependencies', async () => {
const generator = new Generator('/', {}, [
{
Expand Down Expand Up @@ -292,7 +257,7 @@ test('api: onCreateComplete', () => {
api.onCreateComplete(fn)
}
}
], false, cbs)
], cbs)
expect(cbs).toContain(fn)
})

Expand Down Expand Up @@ -333,9 +298,11 @@ test('extract config files', async () => {
api.extendPackage(configs)
}
}
], true)
])

await generator.generate()
await generator.generate({
extractConfigFiles: true
})

const json = v => JSON.stringify(v, null, 2)
expect(fs.readFileSync('/vue.config.js', 'utf-8')).toMatch('module.exports = {\n lintOnSave: true\n}')
Expand Down
73 changes: 69 additions & 4 deletions packages/@vue/cli/__tests__/invoke.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
jest.setTimeout(10000)
jest.setTimeout(12000)
jest.mock('inquirer')

const invoke = require('../lib/invoke')
Expand All @@ -22,13 +22,15 @@ 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')
expect(updatedPkg.eslintConfig).toEqual({
extends: ['plugin:vue/essential', '@vue/airbnb']
})
expect(updatedPkg.gitHooks).toEqual({
'pre-commit': 'lint-staged'
})

const eslintrc = JSON.parse(await project.read('.eslintrc'))
expect(eslintrc).toEqual({
extends: ['plugin:vue/essential', '@vue/airbnb']
})

const lintedMain = await project.read('src/main.js')
expect(lintedMain).toMatch(';') // should've been linted in post-generate hook
}
Expand Down Expand Up @@ -58,3 +60,66 @@ test('invoke with prompts', async () => {
await invoke(`eslint`, {}, project.dir)
await assertUpdates(project)
})

test('invoke with existing files', async () => {
const project = await create(`invoke-existing`, {
useConfigFiles: true,
plugins: {
'@vue/cli-plugin-babel': {},
'@vue/cli-plugin-eslint': { config: 'base' }
}
})
// mock install
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))

// mock existing vue.config.js
await project.write('vue.config.js', `module.exports = { lintOnSave: false }`)

const eslintrc = JSON.parse(await project.read('.eslintrc'))
expect(eslintrc).toEqual({
extends: ['plugin:vue/essential', 'eslint:recommended']
})

await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb --lintOn save,commit`)

await assertUpdates(project)
const updatedVueConfig = await project.read('vue.config.js')
expect(updatedVueConfig).toMatch(`module.exports = { lintOnSave: true }`)
})

test('invoke with existing files (yaml)', async () => {
const project = await create(`invoke-existing`, {
useConfigFiles: true,
plugins: {
'@vue/cli-plugin-babel': {},
'@vue/cli-plugin-eslint': { config: 'base' }
}
})
// mock install
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))

const eslintrc = JSON.parse(await project.read('.eslintrc'))
expect(eslintrc).toEqual({
extends: ['plugin:vue/essential', 'eslint:recommended']
})

await project.rm(`.eslintrc`)
await project.write(`.eslintrc.yml`, `
extends:
- 'plugin:vue/essential'
- 'eslint:recommended'
`.trim())

await project.run(`${require.resolve('../bin/vue')} invoke eslint --config airbnb`)

const updated = await project.read('.eslintrc.yml')
expect(updated).toMatch(`
extends:
- 'plugin:vue/essential'
- '@vue/airbnb'
`.trim())
})
54 changes: 54 additions & 0 deletions packages/@vue/cli/acorn-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const acorn = require('acorn')
const walk = require('acorn/dist/walk')

const ast = acorn.parse(`
module.exports = {
lintOnSave: true,
css: {
loaderOptions: {
sass: {
data: 'foo'
}
}
},
pluginsOptions: {
foo: 'bar'
}
}
`)

let exportsIdentifier = null

walk.simple(ast, {
AssignmentExpression (node) {
if (
node.left.type === 'MemberExpression' &&
node.left.object.name === 'module' &&
node.left.property.name === 'exports'
) {
if (node.right.type === 'ObjectExpression') {
augmentExports(node.right)
} else if (node.right.type === 'Identifier') {
// do a second pass
exportsIdentifier = node.right.name
}
}
}
})

if (exportsIdentifier) {
walk.simple(ast, {
VariableDeclarator (node) {
if (
node.id.name === exportsIdentifier &&
node.init.type === 'ObjectExpression'
) {
augmentExports(node.init)
}
}
})
}

function augmentExports (node) {
console.log(node)
}
5 changes: 3 additions & 2 deletions packages/@vue/cli/lib/Creator.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ module.exports = class Creator {
context,
pkg,
plugins,
preset.useConfigFiles,
createCompleteCbs
)
await generator.generate()
await generator.generate({
extractConfigFiles: preset.useConfigFiles
})

// install additional deps (injected by generators)
log(`📦 Installing additional dependencies...`)
Expand Down
39 changes: 27 additions & 12 deletions packages/@vue/cli/lib/Generator.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
const ejs = require('ejs')
const slash = require('slash')
const debug = require('debug')
const configMap = require('./util/configMap')
const GeneratorAPI = require('./GeneratorAPI')
const sortObject = require('./util/sortObject')
const writeFileTree = require('./util/writeFileTree')
const configTransforms = require('./util/configTransforms')

module.exports = class Generator {
constructor (context, pkg, plugins, extractConfigFiles, completeCbs = []) {
constructor (context, pkg, plugins, completeCbs = []) {
this.context = context
this.plugins = plugins
this.pkg = pkg
this.originalPkg = pkg
this.pkg = Object.assign({}, pkg)
this.completeCbs = completeCbs

// for conflict resolution
Expand All @@ -27,11 +28,14 @@ module.exports = class Generator {
const api = new GeneratorAPI(id, this, options, rootOptions || {})
apply(api, options, rootOptions)
})
// extract configs from package.json into dedicated files.
this.extractConfigFiles(extractConfigFiles)
}

async generate () {
async generate ({
extractConfigFiles = false,
checkExisting = false
} = {}) {
// extract configs from package.json into dedicated files.
this.extractConfigFiles(extractConfigFiles, checkExisting)
// wait for file resolve
await this.resolveFiles()
// set package.json
Expand All @@ -41,21 +45,32 @@ module.exports = class Generator {
await writeFileTree(this.context, this.files)
}

extractConfigFiles (all) {
extractConfigFiles (extractAll, checkExisting) {
const extract = key => {
if (configMap[key]) {
if (
configTransforms[key] &&
this.pkg[key] &&
// do not extract if the field exists in original package.json
!this.originalPkg[key]
) {
const value = this.pkg[key]
const { transform, filename } = configMap[key]
this.files[filename] = transform(value)
const transform = configTransforms[key]
const res = transform(
value,
checkExisting,
this.context
)
const { content, filename } = res
this.files[filename] = content
delete this.pkg[key]
}
}
if (all) {
if (extractAll) {
for (const key in this.pkg) {
extract(key)
}
} else if (!process.env.VUE_CLI_TEST) {
// by default, extract vue.config.js
// by default, always extract vue.config.js
extract('vue')
}
}
Expand Down
7 changes: 3 additions & 4 deletions packages/@vue/cli/lib/GeneratorAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,9 @@ class GeneratorAPI {
* Tool configuration fields may be extracted into standalone files before
* files are written to disk.
*
* @param {object} fields - Fields to merge.
* @param {object} [options] - pass { merge: false } to disable deep merging.
* @param {object | () => object} fields - Fields to merge.
*/
extendPackage (fields, options = { merge: true }) {
extendPackage (fields) {
const pkg = this.generator.pkg
const toMerge = isFunction(fields) ? fields(pkg) : fields
for (const key in toMerge) {
Expand All @@ -117,7 +116,7 @@ class GeneratorAPI {
value,
this.generator.depSources
)
} else if (!options.merge || !(key in pkg)) {
} else if (!(key in pkg)) {
pkg[key] = value
} else if (Array.isArray(value) && Array.isArray(existing)) {
pkg[key] = existing.concat(value)
Expand Down
14 changes: 9 additions & 5 deletions packages/@vue/cli/lib/invoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ async function invoke (pluginName, options = {}, context = process.cwd()) {
context,
pkg,
[plugin],
isTestOrDebug ? false : loadOptions().useConfigFiles,
createCompleteCbs
)

log()
logWithSpinner('🚀', `Invoking generator for ${id}...`)
await generator.generate()
await generator.generate({
extractConfigFiles: true,
checkExisting: true
})

const newDeps = generator.pkg.dependencies
const newDevDeps = generator.pkg.devDependencies
Expand Down Expand Up @@ -115,9 +117,11 @@ async function invoke (pluginName, options = {}, context = process.cwd()) {
log(` Successfully invoked generator for plugin: ${chalk.cyan(id)}`)
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')))
log()
if (stdout.trim()) {
log(` The following files have been updated / added:\n`)
log(chalk.red(stdout.split(/\r?\n/g).map(line => ` ${line}`).join('\n')))
log()
}
}
log(` You should review and commit the changes.`)
log()
Expand Down
Loading

0 comments on commit 46166fb

Please sign in to comment.