Skip to content

Commit f5f4de0

Browse files
authored
feat(GeneratorAPI): allow passing options to api.extendPackage (#5149)
Currently, 3 options are implemented: - options.prune (defaults to `false`) - Remove null or undefined fields from the object after merging. - options.merge (defaults to `true`) deep-merge nested fields, note that dependency fields are always deep merged regardless of this option. - options.warnIncompatibleVersions (defaults to `true`) Output warning if two dependency version ranges don't intersect. Closes #4779
1 parent 9a1d52e commit f5f4de0

File tree

6 files changed

+265
-56
lines changed

6 files changed

+265
-56
lines changed

packages/@vue/cli-plugin-babel/migrator/index.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
const { chalk } = require('@vue/cli-shared-utils')
22

3-
module.exports = (api) => {
4-
api.transformScript('babel.config.js', require('../codemods/usePluginPreset'))
3+
module.exports = api => {
4+
api.transformScript(
5+
'babel.config.js',
6+
require('../codemods/usePluginPreset')
7+
)
58

69
if (api.fromVersion('^3')) {
7-
api.extendPackage({
8-
dependencies: {
9-
'core-js': '^3.6.4'
10-
}
11-
}, true)
10+
api.extendPackage(
11+
{
12+
dependencies: {
13+
'core-js': '^3.6.4'
14+
}
15+
},
16+
{ warnIncompatibleVersions: false }
17+
)
1218

1319
// TODO: implement a codemod to migrate polyfills
1420
api.exitLog(`core-js has been upgraded from v2 to v3.

packages/@vue/cli-plugin-eslint/migrator/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ module.exports = async (api) => {
5454
Object.assign(newDeps, getDeps(api, 'prettier'))
5555
}
5656

57-
api.extendPackage({ devDependencies: newDeps }, true)
57+
api.extendPackage({ devDependencies: newDeps }, { warnIncompatibleVersions: false })
5858

5959
// in case anyone's upgrading from the legacy `typescript-eslint-parser`
6060
if (api.hasPlugin('typescript')) {
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
module.exports = (api) => {
2-
api.extendPackage({
3-
devDependencies: {
4-
typescript: require('../package.json').devDependencies.typescript
5-
}
6-
}, true)
1+
module.exports = api => {
2+
api.extendPackage(
3+
{
4+
devDependencies: {
5+
typescript: require('../package.json').devDependencies.typescript
6+
}
7+
},
8+
{ warnIncompatibleVersions: false }
9+
)
710
}

packages/@vue/cli/__tests__/Generator.spec.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ fs.ensureDirSync(path.resolve(templateDir, '_vscode'))
7373
fs.writeFileSync(path.resolve(templateDir, '_vscode/config.json'), `{}`)
7474
fs.writeFileSync(path.resolve(templateDir, '_gitignore'), 'foo')
7575

76+
beforeEach(() => {
77+
logs.warn = []
78+
})
79+
7680
test('api: extendPackage', async () => {
7781
const generator = new Generator('/', {
7882
pkg: {
@@ -376,6 +380,130 @@ test('api: extendPackage merge warn nonstrictly semver deps', async () => {
376380
})).toBe(true)
377381
})
378382

383+
test('api: extendPackage + { merge: false }', async () => {
384+
const generator = new Generator('/', {
385+
pkg: {
386+
name: 'hello',
387+
list: [1],
388+
vue: {
389+
foo: 1,
390+
bar: 2
391+
}
392+
},
393+
plugins: [{
394+
id: 'test',
395+
apply: api => {
396+
api.extendPackage(
397+
{
398+
name: 'hello2',
399+
list: [2],
400+
vue: {
401+
foo: 2,
402+
baz: 3
403+
}
404+
},
405+
{ merge: false }
406+
)
407+
}
408+
}]
409+
})
410+
411+
await generator.generate()
412+
413+
const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
414+
expect(pkg).toEqual({
415+
name: 'hello2',
416+
list: [2],
417+
vue: {
418+
foo: 2,
419+
baz: 3
420+
}
421+
})
422+
})
423+
424+
test('api: extendPackage + { prune: true }', async () => {
425+
const generator = new Generator('/', {
426+
pkg: {
427+
name: 'hello',
428+
version: '0.0.0',
429+
dependencies: {
430+
foo: '1.0.0'
431+
},
432+
vue: {
433+
bar: 1,
434+
baz: 2
435+
}
436+
},
437+
plugins: [{
438+
id: 'test',
439+
apply: api => {
440+
api.extendPackage(
441+
{
442+
name: null,
443+
dependencies: {
444+
foo: null,
445+
qux: '2.0.0'
446+
},
447+
vue: {
448+
bar: null,
449+
baz: 3
450+
}
451+
},
452+
{ prune: true }
453+
)
454+
}
455+
}]
456+
})
457+
458+
await generator.generate()
459+
460+
const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
461+
expect(pkg).toEqual({
462+
version: '0.0.0',
463+
dependencies: {
464+
qux: '2.0.0'
465+
},
466+
vue: {
467+
baz: 3
468+
}
469+
})
470+
})
471+
472+
test('api: extendPackage + { warnIncompatibleVersions: false }', async () => {
473+
const generator = new Generator('/', {
474+
pkg: {
475+
devDependencies: {
476+
eslint: '^4.0.0'
477+
}
478+
},
479+
plugins: [{
480+
id: 'test',
481+
apply: api => {
482+
api.extendPackage(
483+
{
484+
devDependencies: {
485+
eslint: '^6.0.0'
486+
}
487+
},
488+
{ warnIncompatibleVersions: false }
489+
)
490+
}
491+
}]
492+
})
493+
494+
await generator.generate()
495+
const pkg = JSON.parse(fs.readFileSync('/package.json', 'utf-8'))
496+
497+
// should not warn about the version conflicts
498+
expect(logs.warn.length).toBe(0)
499+
// should use the newer version
500+
expect(pkg).toEqual({
501+
devDependencies: {
502+
eslint: '^6.0.0'
503+
}
504+
})
505+
})
506+
379507
test('api: render fs directory', async () => {
380508
const generator = new Generator('/', { plugins: [
381509
{

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

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require('fs')
22
const ejs = require('ejs')
33
const path = require('path')
4-
const merge = require('deepmerge')
4+
const deepmerge = require('deepmerge')
55
const resolve = require('resolve')
66
const { isBinaryFileSync } = require('isbinaryfile')
77
const mergeDeps = require('./util/mergeDeps')
@@ -14,6 +14,23 @@ const isString = val => typeof val === 'string'
1414
const isFunction = val => typeof val === 'function'
1515
const isObject = val => val && typeof val === 'object'
1616
const mergeArrayWithDedupe = (a, b) => Array.from(new Set([...a, ...b]))
17+
function pruneObject (obj) {
18+
if (typeof obj === 'object') {
19+
for (const k in obj) {
20+
if (!obj.hasOwnProperty(k)) {
21+
continue
22+
}
23+
24+
if (obj[k] == null) {
25+
delete obj[k]
26+
} else {
27+
obj[k] = pruneObject(obj[k])
28+
}
29+
}
30+
}
31+
32+
return obj
33+
}
1734

1835
class GeneratorAPI {
1936
/**
@@ -176,15 +193,34 @@ class GeneratorAPI {
176193

177194
/**
178195
* Extend the package.json of the project.
179-
* Nested fields are deep-merged unless `{ merge: false }` is passed.
180196
* Also resolves dependency conflicts between plugins.
181197
* Tool configuration fields may be extracted into standalone files before
182198
* files are written to disk.
183199
*
184200
* @param {object | () => object} fields - Fields to merge.
185-
* @param {boolean} forceNewVersion - Ignore version conflicts when updating dependency version
201+
* @param {object} [options] - Options for extending / merging fields.
202+
* @param {boolean} [options.prune=false] - Remove null or undefined fields
203+
* from the object after merging.
204+
* @param {boolean} [options.merge=true] deep-merge nested fields, note
205+
* that dependency fields are always deep merged regardless of this option.
206+
* @param {boolean} [options.warnIncompatibleVersions=true] Output warning
207+
* if two dependency version ranges don't intersect.
186208
*/
187-
extendPackage (fields, forceNewVersion) {
209+
extendPackage (fields, options = {}) {
210+
const extendOptions = {
211+
prune: false,
212+
merge: true,
213+
warnIncompatibleVersions: true
214+
}
215+
216+
// this condition statement is added for compatiblity reason, because
217+
// in version 4.0.0 to 4.1.2, there's no `options` object, but a `forceNewVersion` flag
218+
if (typeof options === 'boolean') {
219+
extendOptions.warnIncompatibleVersions = !options
220+
} else {
221+
Object.assign(extendOptions, options)
222+
}
223+
188224
const pkg = this.generator.pkg
189225
const toMerge = isFunction(fields) ? fields(pkg) : fields
190226
for (const key in toMerge) {
@@ -197,18 +233,22 @@ class GeneratorAPI {
197233
existing || {},
198234
value,
199235
this.generator.depSources,
200-
forceNewVersion
236+
extendOptions
201237
)
202-
} else if (!(key in pkg)) {
238+
} else if (!extendOptions.merge || !(key in pkg)) {
203239
pkg[key] = value
204240
} else if (Array.isArray(value) && Array.isArray(existing)) {
205241
pkg[key] = mergeArrayWithDedupe(existing, value)
206242
} else if (isObject(value) && isObject(existing)) {
207-
pkg[key] = merge(existing, value, { arrayMerge: mergeArrayWithDedupe })
243+
pkg[key] = deepmerge(existing, value, { arrayMerge: mergeArrayWithDedupe })
208244
} else {
209245
pkg[key] = value
210246
}
211247
}
248+
249+
if (extendOptions.prune) {
250+
pruneObject(pkg)
251+
}
212252
}
213253

214254
/**

0 commit comments

Comments
 (0)