@@ -224,6 +224,41 @@ const diffKeys = (
224224 . join ( '.' ) ;
225225} ;
226226
227+ /**
228+ * Deeply merges a source object into a target object.
229+ * @param target The target object to merge into.
230+ * @param source The source object to merge.
231+ * @returns the target object with source merged.
232+ */
233+ const deepMerge = ( target : any , source : any ) : any => {
234+ if ( typeof target !== 'object' || target === null ) {
235+ return source ;
236+ }
237+ if ( typeof source !== 'object' || source === null ) {
238+ return source ;
239+ }
240+
241+ for ( const key of Object . keys ( source ) ) {
242+ const sourceValue = source [ key ] ;
243+ const targetValue = target [ key ] ;
244+
245+ if ( Array . isArray ( sourceValue ) && Array . isArray ( targetValue ) ) {
246+ target [ key ] = targetValue . concat ( sourceValue ) ;
247+ } else if (
248+ typeof sourceValue === 'object' &&
249+ typeof targetValue === 'object' &&
250+ sourceValue !== null &&
251+ targetValue !== null
252+ ) {
253+ target [ key ] = deepMerge ( { ...targetValue } , sourceValue ) ;
254+ } else {
255+ target [ key ] = sourceValue ;
256+ }
257+ }
258+
259+ return target ;
260+ } ;
261+
227262/**
228263 * Print diffs
229264 * @param base Base ref
@@ -250,6 +285,9 @@ const printDiffs = (
250285
251286 const groups = new Map < string , Set < string > > ( ) ;
252287
288+ const baseContents = { } ;
289+ const headContents = { } ;
290+
253291 for ( const status of getGitDiffStatuses ( base , head ) ) {
254292 if (
255293 ! (
@@ -260,157 +298,166 @@ const printDiffs = (
260298 continue ;
261299 }
262300
263- const baseContents = (
301+ const baseFileContents = (
264302 status . value !== 'A'
265303 ? JSON . parse ( getFileContent ( base , status . basePath ) )
266304 : { }
267305 ) as CompatData ;
268- const headContents = (
306+ const headFileContents = (
269307 status . value !== 'D'
270308 ? JSON . parse ( getFileContent ( head , status . headPath ) )
271309 : { }
272310 ) as CompatData ;
273311
274- if ( options . mirror ) {
275- for ( const feature of walk ( undefined , baseContents ) ) {
276- applyMirroring ( feature ) ;
277- }
278- for ( const feature of walk ( undefined , headContents ) ) {
279- applyMirroring ( feature ) ;
280- }
281- }
312+ deepMerge ( baseContents , baseFileContents ) ;
313+ deepMerge ( headContents , headFileContents ) ;
314+ }
315+
316+ if ( options . mirror ) {
282317 for ( const feature of walk ( undefined , baseContents ) ) {
283- addVersionLast ( feature ) ;
318+ applyMirroring ( feature ) ;
284319 }
285320 for ( const feature of walk ( undefined , headContents ) ) {
286- addVersionLast ( feature ) ;
321+ applyMirroring ( feature ) ;
287322 }
288- if ( options . transform ) {
289- for ( const feature of walk ( undefined , baseContents ) ) {
290- transformMD ( feature ) ;
291- }
292- for ( const feature of walk ( undefined , headContents ) ) {
293- transformMD ( feature ) ;
294- }
323+ }
324+ for ( const feature of walk ( undefined , baseContents ) ) {
325+ addVersionLast ( feature ) ;
326+ }
327+ for ( const feature of walk ( undefined , headContents ) ) {
328+ addVersionLast ( feature ) ;
329+ }
330+ if ( options . transform ) {
331+ for ( const feature of walk ( undefined , baseContents ) ) {
332+ transformMD ( feature ) ;
333+ }
334+ for ( const feature of walk ( undefined , headContents ) ) {
335+ transformMD ( feature ) ;
295336 }
337+ }
296338
297- const baseData = flattenObject ( baseContents ) ;
298- const headData = flattenObject ( headContents ) ;
339+ const baseData = flattenObject ( baseContents ) ;
340+ const headData = flattenObject ( headContents ) ;
299341
300- const keys = [
301- ...new Set < string > ( [
302- ...Object . keys ( baseData ) ,
303- ...Object . keys ( headData ) ,
304- ] ) . values ( ) ,
305- ] . sort ( ) ;
342+ const keys = [
343+ ...new Set < string > ( [
344+ ...Object . keys ( baseData ) ,
345+ ...Object . keys ( headData ) ,
346+ ] ) . values ( ) ,
347+ ] . sort ( ) ;
306348
307- if ( ! keys . length ) {
308- continue ;
309- }
349+ if ( ! keys . length ) {
350+ console . log ( '✔ No data file changed.' ) ;
351+ return ;
352+ }
310353
311- const prefix = diffArrays (
312- keys . at ( 0 ) ?. split ( '.' ) ?? [ ] ,
313- keys . at ( - 1 ) ?. split ( '.' ) ?? [ ] ,
314- ) [ 0 ] ?. value . join ( '.' ) ;
354+ const prefix = diffArrays (
355+ keys . at ( 0 ) ?. split ( '.' ) ?? [ ] ,
356+ keys . at ( - 1 ) ?. split ( '.' ) ?? [ ] ,
357+ ) [ 0 ] ?. value . join ( '.' ) ;
315358
316- const commonName =
317- options . format === 'html' ? `<h3>${ prefix } </h3>` : `${ prefix } ` ;
359+ const commonName =
360+ options . format === 'html' ? `<h3>${ prefix } </h3>` : `${ prefix } ` ;
318361
319- let lastKey = '' ;
362+ let lastKey = '' ;
320363
321- for ( const key of keys ) {
322- const baseValue = JSON . stringify ( baseData [ key ] ?? null ) ;
323- const headValue = JSON . stringify ( headData [ key ] ?? null ) ;
324- if ( baseValue === headValue ) {
325- continue ;
326- }
327- if ( ! lastKey ) {
328- lastKey = key ;
329- }
330- const keyDiff = diffKeys (
331- key . slice ( prefix . length ) ,
332- lastKey . slice ( prefix . length ) ,
333- options ,
334- ) ;
364+ for ( const key of keys ) {
365+ const baseValue = JSON . stringify ( baseData [ key ] ?? null ) ;
366+ const headValue = JSON . stringify ( headData [ key ] ?? null ) ;
367+ if ( baseValue === headValue ) {
368+ continue ;
369+ }
370+ if ( ! lastKey ) {
371+ lastKey = key ;
372+ }
373+ const keyDiff = diffKeys (
374+ key . slice ( prefix . length ) ,
375+ lastKey . slice ( prefix . length ) ,
376+ options ,
377+ ) ;
335378
336- const splitRegexp =
337- / (?< = ^ " ) | (?< = [ \] , / ] ) | (? = [ [ , / ] ) | (? = " $ ) | (?< = \d ) (? = − ) | (?< = − ) (? = \d ) | (? = # ) / ;
338- let headValueForDiff = headValue ;
339- let baseValueForDiff = baseValue ;
379+ const splitRegexp =
380+ / (?< = ^ " ) | (?< = [ \] , / ] ) | (? = [ [ , / ] ) | (? = " $ ) | (?< = \d ) (? = − ) | (?< = − ) (? = \d ) | (? = # ) / ;
381+ let headValueForDiff = headValue ;
382+ let baseValueForDiff = baseValue ;
340383
341- if ( baseValue == 'null' ) {
342- baseValueForDiff = '' ;
343- if ( headValue == '"mirror"' || headValue == '"false"' ) {
344- // Ignore initial "mirror"/"false" values.
345- headValueForDiff = '' ;
346- }
347- } else if ( headValue == 'null' ) {
384+ if ( baseValue == 'null' ) {
385+ baseValueForDiff = '' ;
386+ if ( headValue == '"mirror"' || headValue == '"false"' ) {
387+ // Ignore initial "mirror"/"false" values.
348388 headValueForDiff = '' ;
349389 }
390+ } else if ( headValue == 'null' ) {
391+ headValueForDiff = '' ;
392+ }
350393
351- const valueDiff = diffArrays (
352- headValueForDiff . split ( splitRegexp ) ,
353- baseValueForDiff . split ( splitRegexp ) ,
354- )
355- . map ( ( part ) => {
356- // Note: removed/added is deliberately inversed here, to have additions first.
357- const value = part . value . join ( '' ) ;
358- if ( part . removed ) {
359- return options . format == 'html'
360- ? `<ins style="color: green">${ value } </ins>`
361- : chalk `{green ${ value } }` ;
362- } else if ( part . added ) {
363- return options . format == 'html'
364- ? `<del style="color: red">${ value } </del>`
365- : chalk `{red ${ value } }` ;
366- }
394+ const valueDiff = diffArrays (
395+ headValueForDiff . split ( splitRegexp ) ,
396+ baseValueForDiff . split ( splitRegexp ) ,
397+ )
398+ . map ( ( part ) => {
399+ // Note: removed/added is deliberately inversed here, to have additions first.
400+ const value = part . value . join ( '' ) ;
401+ if ( part . removed ) {
402+ return options . format == 'html'
403+ ? `<ins style="color: green">${ value } </ins>`
404+ : chalk `{green ${ value } }` ;
405+ } else if ( part . added ) {
406+ return options . format == 'html'
407+ ? `<del style="color: red">${ value } </del>`
408+ : chalk `{red ${ value } }` ;
409+ }
367410
368- return value ;
369- } )
370- . join ( '' ) ;
411+ return value ;
412+ } )
413+ . join ( '' ) ;
371414
372- const value = valueDiff ;
415+ const value = valueDiff ;
373416
374- if ( ! value . length ) {
375- // e.g. null => "mirror"
376- continue ;
377- }
417+ if ( ! value . length ) {
418+ // e.g. null => "mirror"
419+ continue ;
420+ }
378421
379- if ( options . group ) {
380- const reverseKeyParts = key . split ( '.' ) . reverse ( ) ;
381- const browser = reverseKeyParts . find ( ( part ) =>
382- BROWSER_NAMES . includes ( part ) ,
383- ) ;
384- const field = reverseKeyParts . find ( ( part ) => ! / ^ \d + $ / . test ( part ) ) ;
385- const groupKey = `${ ! browser ? '' : options . format == 'html' ? `<strong>${ browser } </strong>.` : chalk `{cyan ${ browser } }.` } ${ field } = ${ value } ` ;
386- const groupValue = key
387- . split ( '.' )
388- . map ( ( part ) => ( part !== browser && part !== field ? part : '{}' ) )
389- . reverse ( )
390- . filter ( ( value , index ) => index > 0 || value !== '{}' )
391- . reverse ( )
392- . map ( ( value ) =>
393- value !== '{}'
394- ? value
395- : options . format == 'html'
396- ? '<small>{}</small>'
397- : chalk `{dim \{\}}` ,
398- )
399- . join ( '.' ) ;
400- const group = groups . get ( groupKey ) ?? new Set ( ) ;
401- group . add ( groupValue ) ;
402- groups . set ( groupKey , group ) ;
403- } else {
404- const change =
405- options . format == 'html'
406- ? `${ keyDiff } = ${ value } `
407- : chalk `${ keyDiff } = ${ value } ` ;
408- const group = groups . get ( commonName ) ?? new Set ( ) ;
409- group . add ( change ) ;
410- groups . set ( commonName , group ) ;
411- }
412- lastKey = key ;
422+ if ( options . group ) {
423+ const reverseKeyParts = key . split ( '.' ) . reverse ( ) ;
424+ const browser = reverseKeyParts . find ( ( part ) =>
425+ BROWSER_NAMES . includes ( part ) ,
426+ ) ;
427+ const field = reverseKeyParts . find ( ( part ) => ! / ^ \d + $ / . test ( part ) ) ;
428+ const groupKey = `${ ! browser ? '' : options . format == 'html' ? `<strong>${ browser } </strong>.` : chalk `{cyan ${ browser } }.` } ${ field } = ${ value } ` ;
429+ const groupValue = key
430+ . split ( '.' )
431+ . map ( ( part ) => ( part !== browser && part !== field ? part : '{}' ) )
432+ . reverse ( )
433+ . filter ( ( value , index ) => index > 0 || value !== '{}' )
434+ . reverse ( )
435+ . map ( ( value ) =>
436+ value !== '{}'
437+ ? value
438+ : options . format == 'html'
439+ ? '<small>{}</small>'
440+ : chalk `{dim \{\}}` ,
441+ )
442+ . join ( '.' ) ;
443+ const group = groups . get ( groupKey ) ?? new Set ( ) ;
444+ group . add ( groupValue ) ;
445+ groups . set ( groupKey , group ) ;
446+ } else {
447+ const change =
448+ options . format == 'html'
449+ ? `${ keyDiff } = ${ value } `
450+ : chalk `${ keyDiff } = ${ value } ` ;
451+ const group = groups . get ( commonName ) ?? new Set ( ) ;
452+ group . add ( change ) ;
453+ groups . set ( commonName , group ) ;
413454 }
455+ lastKey = key ;
456+ }
457+
458+ if ( groups . size === 0 ) {
459+ console . log ( '✔ No changes.' ) ;
460+ return ;
414461 }
415462
416463 const originalEntries : [ string , string [ ] ] [ ] = [ ...groups . entries ( ) ] . map (
0 commit comments