UNPKG

json-conflict-resolver

Version:

A rules-based JSON conflict resolver that parses Git conflict markers, reconstructs ours/theirs, and merges with deterministic strategies — beyond line-based merges.

11 lines (10 loc) 15.3 kB
"use strict";var dt=Object.create;var R=Object.defineProperty;var yt=Object.getOwnPropertyDescriptor;var wt=Object.getOwnPropertyNames;var ht=Object.getPrototypeOf,St=Object.prototype.hasOwnProperty;var Ct=(t,e)=>{for(var r in e)R(t,r,{get:e[r],enumerable:!0})},U=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of wt(e))!St.call(t,n)&&n!==r&&R(t,n,{get:()=>e[n],enumerable:!(s=yt(e,n))||s.enumerable});return t};var S=(t,e,r)=>(r=t!=null?dt(ht(t)):{},U(e||!t||!t.__esModule?R(r,"default",{value:t,enumerable:!0}):r,t)),xt=t=>U(R({},"__esModule",{value:!0}),t);var _t={};Ct(_t,{resolveConflicts:()=>Lt});module.exports=xt(_t);var B=require("child_process"),J=require("util"),W=(0,J.promisify)(B.execFile),H=async(t,e)=>{var h;let r=t.split(` `),s=[],n=[],o;(f=>(f[f.Normal=0]="Normal",f[f.InOurs=1]="InOurs",f[f.InTheirs=2]="InTheirs"))(o||(o={}));let a=0;for(let d of r){if(d.startsWith("<<<<<<<")){a=1;continue}else if(d.startsWith("=======")){a===1&&(a=2);continue}else if(d.startsWith(">>>>>>>")){a===2&&(a=0);continue}switch(a){case 0:s.push(d),n.push(d);break;case 1:s.push(d);break;case 2:n.push(d);break}}let c=s.join(` `),u=n.join(` `),l=await W("git",["show",`:1:${e.filename}`],{maxBuffer:1024*1024*50}).then(({stdout:d})=>d).catch(()=>null);if(c===u&&(c=(h=await W("git",["show",`HEAD:${e.filename}`],{maxBuffer:1024*1024*50}).then(({stdout:d})=>d).catch(()=>null))!=null?h:c),!c||!u)throw new Error("Conflict parsing resulted in empty content.");let p=bt(e),[g,i]=await K(c,p),[[w],y]=await Promise.all((l?[u,l]:[u]).map(d=>K(d,[i])));return{ours:g,theirs:w,base:y==null?void 0:y[0],format:typeof i=="string"?i:i.name}},Tt={json:"json",json5:"json5",yaml:"yaml",yml:"yaml",toml:"toml",xml:"xml"},bt=t=>{var e,r;if(Array.isArray(t.parsers))return t.parsers;if(t.parsers)return t.parsers==="auto"?["json","json5","yaml","toml","xml"]:[t.parsers];if(t.filename){let s=Tt[(r=(e=t.filename.split(".").pop())==null?void 0:e.toLowerCase())!=null?r:""];if(s)return[s]}return["json"]},K=async(t,e)=>{for(let r of e)try{return typeof r!="string"?[r.parser(t),r]:[await Mt(r,t),r]}catch(s){console.debug(`Parser ${typeof r=="string"?r:r.name} failed:`,s)}throw new Error(`Failed to parse content. Tried parsers: ${e.map(r=>typeof r=="string"?r:r.name).join(", ")}`)},Mt=async(t,e)=>{switch(t){case"json":return JSON.parse(e);case"json5":try{let{parse:r}=await import("json5");return r(e)}catch{throw new Error("json5 parser not installed. Please install as peer dependency.")}case"yaml":try{let{parse:r}=await import("yaml");return r(e)}catch{throw new Error("yaml parser not installed. Please install as peer dependency.")}case"toml":try{let{parse:r}=await import("smol-toml");return r(e)}catch{throw new Error("toml parser not installed. Please install as peer dependency.")}case"xml":try{let{XMLParser:r}=await import("fast-xml-parser");return new r().parse(e)}catch{throw new Error("fast-xml-parser not installed. Please install as peer dependency.")}}};var G={isMatch:(t,e)=>{let r=M(t);return e.some(s=>Pt(r,M(s)))}},V=async t=>{if(t==="micromatch"){let e;try{e=await import("micromatch")}catch{throw new Error("micromatch is not installed. Please add it as a dependency if you want to use it.")}return{isMatch:(r,s)=>{try{return e.isMatch(M(r),s.map(M))}catch(n){throw new Error(`micromatch failed to run isMatch: ${n.message}`)}}}}if(t==="picomatch"){let e;try{e=(await import("picomatch")).default}catch{throw new Error("picomatch is not installed. Please add it as a dependency if you want to use it.")}return{isMatch:(r,s)=>{try{return e(s.map(M))(M(r))}catch(n){throw new Error(`picomatch failed to run isMatch: ${n.message}`)}}}}throw new Error(`Unknown matcher name: ${t}`)},M=t=>t.replace(/\\[./]|\./g,e=>e==="\\."?"\0":e==="\\/"?"":"/"),$t=t=>{let e=t.startsWith("!"),r=e?t.slice(1):t,s=[],n="",o=!1;for(let a of r)o?(n+="\\"+a,o=!1):a==="\\"?o=!0:a==="/"?(s.push(n),n=""):n+=a;return o&&(n+="\\"),s.push(n),e&&(s.negated=!0),s},Pt=(t,e)=>{let r=t.split("/"),s=$t(e),n=s.negated===!0,o=X(r,s);return n?!o:o},X=(t,e)=>{let r=0,s=0;for(;r<t.length&&s<e.length;){let n=e[s];if(n==="**"){if(s===e.length-1)return!0;for(let o=0;r+o<=t.length;o++)if(X(t.slice(r+o),e.slice(s+1)))return!0;return!1}if(!At(t[r],n))return!1;r++,s++}for(;s<e.length&&e[s]==="**";)s++;return r===t.length&&s===e.length},At=(t,e)=>{let r=!1,s=!1,n="",o="";for(let u=0;u<e.length;u++){let l=e[u];if(r){o+=l,r=!1;continue}if(l==="\\"){r=!0;continue}if(l==="*"){if(!s){s=!0,n=o,o="";continue}o+="*";continue}o+=l}if(r&&(o+="\\"),!s){let u=o.replace(/\\(.)/g,"$1");return t===u}let a=o.replace(/\\(.)/g,"$1"),c=n.replace(/\\(.)/g,"$1");return t.startsWith(c)&&t.endsWith(a)};var kt={defaultStrategy:["merge","ours"],include:["**/*.json","**/*.yaml","**/*.yml","**/*.xml","**/*.toml"],exclude:["**/node_modules/**","**/dist/**"],debug:!1,writeConflictSidecar:!1},Z=async t=>{let{rules:e,byStrategy:r,defaultStrategy:s,matcher:n,plugins:o,pluginConfig:a,customStrategies:c,...u}={...kt,...t},l=await Et(o,a),p={...c,...l},g={exact:Object.create(null),exactFields:Object.create(null),patterns:Object.create(null),default:Ft(s)},i=typeof n=="string"?await V(n):n!=null?n:G,w=0;if(r)for(let[y,h]of Object.entries(r)){if(!h)continue;let{name:d,important:b}=q(y);for(let m of h){let{key:f,important:x}=Q(m);Y(g,f,{strategies:[{name:d,important:b||x}],order:w++,source:f})}}return e&&et(e,[],(y,h)=>{let{key:d,important:b}=Q(y),m=h.map(f=>{let{name:x,important:T}=q(f);return{name:x,important:T||b}});Y(g,d,{strategies:m,order:w++,source:d})}),{...u,rules:g,matcher:i,customStrategies:p}},Ft=t=>(Array.isArray(t)?t:[t]).map(r=>{let s=r.endsWith("!");return tt(r.slice(0,-1)),{name:s?r.slice(0,-1):r,important:s}}),q=t=>{let e=t.endsWith("!"),r=e?t.slice(0,-1):t;return tt(r),{name:r,important:e}},tt=t=>{if(t.endsWith("!"))throw new Error(`Strategy name "${t}" must not end with "!". Use "!" on field/glob to mark rule importance.`)},Q=t=>{let e=t.endsWith("!"),r=e?t.slice(0,-1):t;if(!r)throw new Error(`Invalid rule key "${t}".`);return{key:r,important:e}},Y=(t,e,r)=>{if(/^\[.*\]$/.test(e)){let n=e.slice(1,-1);if(n.replace(/\\\./g,"").includes(".")||n.trim()==="")throw new Error(`Invalid bracket form "${e}". Use a single bare key like "[id]".`);let o=`**.${n}.**`;O(t.patterns,o,r);return}if(/[*?[\]]/.test(e)){O(t.patterns,e,r);return}e.includes(".")?O(t.exact,e,r):O(t.exactFields,e,r)},et=(t,e,r)=>{for(let[s,n]of Object.entries(t)){let o=[...e,s];Array.isArray(n)?r(o.join("."),n):et(n,o,r)}},O=(t,e,r)=>{var n;((n=t[e])!=null?n:t[e]=[]).push(r)},Et=async(t,e)=>{if(!(t!=null&&t.length))return{};let r={};for(let s of t)try{let n=await import(s),o=n.default||n,a=e==null?void 0:e[s],c=o instanceof Function?await o(a):o;if(!c.strategies)throw new Error(`Plugin "${s}" does not export strategies`);c.init&&await c.init(a),Object.assign(r,c.strategies)}catch(n){throw new Error(`Failed to load plugin "${s}": ${n}`)}return r};var $=S(require("fs/promises")),P=S(require("path")),F=Symbol("MERGE_DROP"),rt=".merge-backups",C=0,A=1,st=2,I=3,Rt=t=>t.includes("<<<<<<<")&&t.includes("=======")&&t.includes(">>>>>>>"),nt=async({root:t=process.cwd(),include:e,exclude:r,matcher:s,includeNonConflicted:n,debug:o,backupDir:a=rt})=>{try{await $.default.rm(a,{recursive:!0,force:!0})}catch{}for(let g of[...e,...r]){if(g.startsWith("!"))throw new Error(`Negation not allowed in include/exclude: ${g}`);g.includes("\\")&&console.warn(`Use '/' as path separator: ${g}`)}r.push(`${a}/**`);let c=g=>{let i=g.replace(/\\/g,"/");return s.isMatch(i,e)&&!s.isMatch(i,r)},u=Ot(e,a?[...r,a]:r,s),l=[],p=async g=>{let i=await $.default.readdir(g,{withFileTypes:!0});for(let w of i){let y=P.default.join(g,w.name),h=P.default.relative(t,y).replace(/\\+/g,"/");if(w.isDirectory())!/node_modules|\.git/.test(w.name)&&!u(h)&&await p(y);else if(c(h))try{let d=await $.default.readFile(y,"utf8");n||Rt(d)?l.push({filePath:h,content:d}):o&&console.info(`Skipped (no conflicts): ${y}`)}catch{console.warn(`Skipped (unreadable): ${y}`)}}};return await p(t),l},Ot=(t,e,r)=>{if(t.length>0&&t.every(o=>!o.includes("/")&&!o.includes("**")))return()=>!1;let s=new Set,n=new Set;for(let o of t)if(o.includes("/")){let a=o.split("/").slice(0,-1).join("/");a&&s.add(a)}s.has("**")&&s.clear();for(let o of e)if(o.endsWith("/**")){let a=o.slice(0,-3);a&&n.add(a)}return o=>r.isMatch(o,[...n])||s.size>0&&!r.isMatch(o,[...s])},ot=async(t,e=rt)=>{let r=P.default.relative(process.cwd(),t),s=P.default.join(e,r);return await $.default.mkdir(P.default.dirname(s),{recursive:!0}),await $.default.copyFile(t,s),s};var at=S(require("fs")),j=require("fs"),N=S(require("path")),it=async(t={},e)=>{var g,i,w,y,h,d,b;let r=(g=t.mode)!=null?g:"memory",s=(i=t.logDir)!=null?i:".logs",n=(w=t.singleFile)!=null?w:!1,o={stdout:(h=(y=t.levels)==null?void 0:y.stdout)!=null?h:e?["debug","info","warn","error"]:["warn","error"],file:(b=(d=t.levels)==null?void 0:d.file)!=null?b:e?["info","debug","warn","error"]:["error"]};try{await j.promises.mkdir(s,{recursive:!0})}catch(m){console.warn(`Failed to create log directory: ${m}`)}let a=new Map,c=new Map,u=m=>{let f=n?"all":m;if(!c.has(f)){let x=N.default.join(s,n?"combined.log":`${m}.log`);c.set(f,at.default.createWriteStream(x,{flags:"a"}))}return c.get(f)},l=(m,f,x)=>{let T={timestamp:new Date().toISOString(),level:f,message:x};o.stdout.includes(f)&&(f==="error"?console.error:console.log)(`[${m}] [${T.timestamp}] [${f.toUpperCase()}] ${T.message}`),o.file.includes(f)&&(r==="memory"?(a.has(m)||a.set(m,[]),a.get(m).push(T)):u(m).write(`[${T.timestamp}] [${f.toUpperCase()}] ${T.message} `))};return{info:(m,f)=>l(m,"info",f),warn:(m,f)=>l(m,"warn",f),error:(m,f)=>l(m,"error",f),debug:(m,f)=>l(m,"debug",f),flush:async()=>{if(r==="memory"){let m=new Date().toISOString().replace(/:/g,"-"),f=Array.from(a.entries()).map(async([x,T])=>{try{let k=N.default.join(s,n?`combined-${m}.log`:`${x}-${m}.log`),pt=T.map(_=>`[${_.timestamp}] [${_.level.toUpperCase()}] ${_.message}`);await j.promises.mkdir(N.default.dirname(k),{recursive:!0}),await j.promises.appendFile(k,pt.join(` `)+` `)}catch(k){console.warn(`Failed to write log file for ${x}: ${k}`)}});await Promise.all(f)}for(let m of c.values())try{m.end()}catch(f){console.warn(`Failed to close log stream: ${f}`)}await new Promise(m=>setTimeout(m,10))}}};var E=async(t,e)=>{switch(t){case"json":case"json5":return JSON.stringify(e,null,2);case"yaml":{let{stringify:r}=await import("yaml");return r(e)}case"toml":{let{stringify:r}=await import("smol-toml");return r(e)}case"xml":{let{XMLBuilder:r}=await import("fast-xml-parser");return new r({}).build(e)}default:throw new Error(`Unknown format: ${t}`)}};var ct=(t,{rules:e,matcher:r})=>{var l,p,g;let s=(l=e.exact[t])!=null?l:[],n=(g=e.exactFields[(p=t.split(".").pop())!=null?p:""])!=null?g:[],o=Object.entries(e.patterns).filter(([i])=>r.isMatch(t,[i])).flatMap(([,i])=>i),a=[...s.flatMap(i=>i.strategies),...n.flatMap(i=>i.strategies),...o.flatMap(i=>i.strategies),...e.default],c=a.filter(i=>i.important),u=a.filter(i=>!i.important);return[...c,...u].map(i=>i.name)};var v=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),jt={ours:({ours:t})=>({status:C,value:t}),theirs:({theirs:t})=>({status:C,value:t}),base:({base:t})=>({status:C,value:t}),drop:t=>({status:C,value:F}),skip:({path:t})=>({status:I,reason:`Skip strategy applied at ${t}`}),"non-empty":({ours:t,theirs:e,base:r})=>t!=null&&t!==""?{status:C,value:t}:e!=null&&e!==""?{status:C,value:e}:r!=null&&r!==""?{status:C,value:r}:{status:A},update:({ours:t,theirs:e})=>t!==void 0?{status:C,value:e}:{status:C,value:F},merge:async t=>{let{ours:e,theirs:r,base:s,path:n,filePath:o,ctx:a,conflicts:c,logger:u}=t;if(v(e)&&v(r)){let l=new Set([...Object.keys(e),...Object.keys(r)]),p={};for(let g of l){let i=g.replace(/\./g,"\0").replace(/\\/g,"");p[g]=await z({ours:e[g],theirs:r[g],base:v(s)?s[g]:void 0,path:n?`${n}.${i}`:i,filePath:o,ctx:a,conflicts:c,logger:u})}return{status:C,value:p}}return{status:A,reason:"Unmergeable type"}},concat:({ours:t,theirs:e,path:r})=>Array.isArray(t)&&Array.isArray(e)?{status:C,value:[...t,...e]}:{status:A,reason:`Cannot concat at ${r}`},unique:({ours:t,theirs:e,path:r})=>Array.isArray(t)&&Array.isArray(e)?{status:C,value:[...new Set([...t,...e])]}:{status:A,reason:`Cannot concat at ${r}`}},z=async({ours:t,theirs:e,base:r,path:s,filePath:n,ctx:o,conflicts:a,logger:c})=>{var l;if(t===e)return t;o._strategyCache||(o._strategyCache=new Map);let u=o._strategyCache.get(s);u||(u=ct(s,o.config),o._strategyCache.set(s,u)),c.debug(n!=null?n:"all",`path: ${s}, strategies: ${u.join(", ")||"none"}`);for(let p of u){c.debug(n!=null?n:"all",`Applying strategy '${p}' at ${s}`);let g=(l=jt[p])!=null?l:o.strategies[p];if(!g)continue;let i=await g({ours:t,theirs:e,base:r,path:s,filePath:n,ctx:o,conflicts:a,logger:c});switch(i.status){case C:return i.value;case A:continue;case I:a.push({path:s,reason:i.reason});return;case st:throw a.push({path:s,reason:i.reason}),new Error(`Merge failed at ${s}: ${i.reason}`)}}a.push({path:s,reason:`All strategies failed (tried: ${u.join(", ")||"none"})`,...o.config.debug?{ours:t,theirs:e,base:r}:{}})};var D=(t,e,r=[])=>{if(t!==F){if(t===void 0){let s=`__CONFLICT_MARKER::${e}__`;return r.push(e),s}if(Array.isArray(t))return t.map((s,n)=>D(s,`${e}[${n}]`,r)).filter(s=>s!==void 0);if(t&&typeof t=="object"){let s={};for(let[n,o]of Object.entries(t)){let a=D(o,e?`${e}.${n}`:n,r);a!==void 0&&(s[n]=a)}return s}return t}},ut=async(t,e,r,s)=>{let n=[],o=D(t,"",n),a=await E(s,o);for(let c of n){let u=lt(e,c),l=lt(r,c),[p,g]=await Promise.all([u,l].map(y=>E(s,y))),i=["<<<<<<< ours",gt(p,2),"=======",gt(g,2),">>>>>>> theirs"].join(` `),w=`__CONFLICT_MARKER::${c}__`;a=a.replace(/json/.test(s)?JSON.stringify(w):w,i)}return a},lt=(t,e)=>{let r=e.replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean),s=t;for(let n of r)s=s==null?void 0:s[n];return s},gt=(t,e)=>{let r=" ".repeat(e);return t.split(` `).map(s=>s&&r+s).join(` `)};var L=S(require("fs/promises"));var Nt=new Map,mt=async({ours:t,theirs:e,base:r,format:s,filePath:n,config:o,normalizedConfig:a,logger:c,autoStage:u=!1})=>{var g;let l=[],[p]=await Promise.all([z({ours:t,theirs:e,base:r,filePath:n,conflicts:l,path:"",ctx:{config:a,strategies:(g=a.customStrategies)!=null?g:{},_strategyCache:Nt},logger:c}),ot(n,o.backupDir)]);if(c.debug(n,JSON.stringify({merged:p,conflicts:l},null,2)),l.length===0){let i=await E(s,p);if(await L.default.writeFile(n,i,"utf8"),u)try{(0,B.execSync)(`git add ${n}`)}catch(w){c.warn(n,`Failed to stage file: ${w}`)}return{success:!0,conflicts:[]}}else{let i=await ut(p,t,e,s);return await Promise.all([L.default.writeFile(n,i,"utf8"),o.writeConflictSidecar?L.default.writeFile(`${n}.conflict.json`,JSON.stringify(l,null,2)):null]),{success:!1,conflicts:l}}};var Lt=async t=>{let e=await it(t.loggerConfig,t.debug),r=await Z(t),s=await nt(r);await Promise.all(s.map(async({filePath:n,content:o})=>{var p;let{theirs:a,ours:c,base:u,format:l}=await H(o,{filename:n,parsers:r.parsers});e.debug(n,JSON.stringify({ours:c,theirs:a,base:u,format:l},null,2)),await mt({ours:c,theirs:a,base:u,format:l,filePath:n,config:t,normalizedConfig:r,logger:e,autoStage:(p=r.autoStage)!=null?p:!0})})),await e.flush()};0&&(module.exports={resolveConflicts});