11const fs = require ( 'fs' )
22const ejs = require ( 'ejs' )
33const path = require ( 'path' )
4- const merge = require ( 'deepmerge' )
4+ const deepmerge = require ( 'deepmerge' )
55const resolve = require ( 'resolve' )
66const { isBinaryFileSync } = require ( 'isbinaryfile' )
77const mergeDeps = require ( './util/mergeDeps' )
@@ -14,6 +14,23 @@ const isString = val => typeof val === 'string'
1414const isFunction = val => typeof val === 'function'
1515const isObject = val => val && typeof val === 'object'
1616const 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
1835class 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