import { JsonPath , PathMapping } from "@azure-tools/datastore" ; import { walk } from "@azure-tools/json" ; import { Stringify , YamlNode , walkYamlAst } from "@azure-tools/yaml" ; param a param b param path export function strictMerge ( a , b , path = [ ] ) { if ( a === null || b === null ) { throw new Error ( `Argument cannot be null ('${ Stringify ( path ) }')` ) ; } if ( a === b || JSON . stringify ( a ) === JSON . stringify ( b ) ) { return a ; } if ( typeof a === "object" && typeof b === "object" ) { if ( a instanceof Array && b instanceof Array ) { if ( a . length === 0 ) { return b ; } if ( b . length === 0 ) { return a ; } throw new Error ( `'${ Stringify ( path ) }' has two arrays that are incompatible (${ Stringify ( a ) }, ${ Stringify ( b ) }).` ) ; } else { const result = { } ; const keys = new Set ( [ ... Object . keys ( a ) , ... Object . keys ( b ) ] ) ; for ( const key of keys ) { const subpath = path . concat ( key ) ; if ( a [ key ] === undefined ) { result [ key ] = b [ key ] ; continue ; } if ( b [ key ] === undefined ) { result [ key ] = a [ key ] ; continue ; } const aMember = a [ key ] ; const bMember = b [ key ] ; result [ key ] = strictMerge ( aMember , bMember , subpath ) ; } return result ; } } throw new Error ( `'${ Stringify ( path ) }' has incompatible values (${ Stringify ( a ) }, ${ Stringify ( b ) }).` ) ; } const macroRegEx = ( ) => /\$\(([a-zA-Z0-9_-]*)\)/gi ; param value param propertyName param higherPriority param lowerPriority param jsAware export function resolveRValue ( value , propertyName , higherPriority , lowerPriority , jsAware = 0 ) { if ( value ) { const resolve = ( macroExpression , macroKey ) => { if ( higherPriority && higherPriority [ macroKey ] ) { return resolveRValue ( higherPriority [ macroKey ] , macroKey , lowerPriority , null , jsAware - 1 ) ; } if ( lowerPriority ) { const keys = Object . getOwnPropertyNames ( lowerPriority ) ; const macroKeyLocation = keys . indexOf ( macroKey ) ; if ( macroKeyLocation > - 1 ) { if ( macroKeyLocation < keys . indexOf ( propertyName ) ) { return resolveRValue ( lowerPriority [ macroKey ] , macroKey , higherPriority , lowerPriority , jsAware - 1 ) ; } } } return macroExpression ; } ; if ( typeof value === "string" ) { const match = macroRegEx ( ) . exec ( value . trim ( ) ) ; if ( match ) { if ( match [ 0 ] === match . input ) { if ( jsAware > 0 ) { return JSON . stringify ( resolve ( match [ 0 ] , match [ 1 ] ) ) ; } return resolve ( match [ 0 ] , match [ 1 ] ) ; } return value . replace ( macroRegEx ( ) , resolve ) ; } } if ( value instanceof Array ) { return value . map ( ( x ) => resolveRValue ( x , "" , higherPriority || lowerPriority , null ) ) ; } } if ( jsAware > 0 ) { return JSON . stringify ( value ) ; } return value ; } export type ArrayMergingStrategy = "high-pri-first" | "low-pri-first" ; export interface MergeOptions { interpolationContext ? : any ; arrayMergeStrategy ? : ArrayMergingStrategy ; concatListPathFilter ? : ( path : JsonPath ) => boolean ; } const defaultOptions = { arrayMergeStrategy : "high-pri-first" , concatListPathFilter : ( ) => false , } ; export function mergeOverwriteOrAppend ( higherPriority , lowerPriority , options = { } , path = [ ] ) { if ( higherPriority === null || lowerPriority === null ) { return null ; } const computedOptions = { ... defaultOptions , ... options , interpolationContext : options . interpolationContext ?? higherPriority , } ; if ( higherPriority === true && typeof lowerPriority === "object" ) { return lowerPriority ; } if ( typeof higherPriority !== "object" || higherPriority instanceof Array || typeof lowerPriority !== "object" || lowerPriority instanceof Array ) { return mergeArray ( higherPriority , lowerPriority , path , computedOptions ) ; } const result = { } ; const keys = getKeysInOrder ( higherPriority , lowerPriority , computedOptions ) ; for ( const key of keys ) { const subpath = path . concat ( key ) ; if ( higherPriority [ key ] === undefined ) { result [ key ] = resolveRValue ( lowerPriority [ key ] , key , computedOptions . interpolationContext , lowerPriority ) ; continue ; } if ( lowerPriority [ key ] === undefined ) { result [ key ] = resolveRValue ( higherPriority [ key ] , key , null , computedOptions . interpolationContext ) ; continue ; } const aMember = resolveRValue ( higherPriority [ key ] , key , lowerPriority , computedOptions . interpolationContext ) ; const bMember = resolveRValue ( lowerPriority [ key ] , key , computedOptions . interpolationContext , lowerPriority ) ; result [ key ] = mergeOverwriteOrAppend ( aMember , bMember , { ... computedOptions , interpolationContext : computedOptions . interpolationContext [ key ] } , subpath ) ; } return result ; } param higherPriority param lowerPriority param options returns function getKeysInOrder ( higherPriority , lowerPriority , options ) { const lowPriKeys = Object . getOwnPropertyNames ( lowerPriority ) ; const highPriKeys = Object . getOwnPropertyNames ( higherPriority ) ; return [ ... new Set ( options . arrayMergeStrategy === "low-pri-first" ? lowPriKeys . concat ( highPriKeys ) : highPriKeys . concat ( lowPriKeys ) ) , ] ; } function mergeArray ( higherPriority , lowerPriority , path , { concatListPathFilter , arrayMergeStrategy } ) { if ( ! ( higherPriority instanceof Array ) && ! ( lowerPriority instanceof Array ) && ! concatListPathFilter ( path ) ) { return higherPriority ; } const higherPriorityArray = higherPriority instanceof Array ? higherPriority : [ higherPriority ] ; const lowerPriorityArray = lowerPriority instanceof Array ? lowerPriority : [ lowerPriority ] ; if ( arrayMergeStrategy === "high-pri-first" ) { return [ ... new Set ( higherPriorityArray . concat ( lowerPriority ) ) ] ; } else { return [ ... new Set ( lowerPriorityArray . concat ( higherPriority ) ) ] ; } }