git-json-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.
427 lines (335 loc) • 11.9 kB
Markdown
# Git Json Resolver <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 40px"/>

[](https://github.com/react18-tools/git-json-resolver/actions/workflows/test.yml)
[](https://qlty.sh/gh/react18-tools/projects/git-json-resolver)
[](https://codecov.io/gh/react18-tools/git-json-resolver)
[](https://www.npmjs.com/package/git-json-resolver)
[](https://www.npmjs.com/package/git-json-resolver)

A Git-aware conflict resolver for **JSON-first structured data**.
## Why?
- Git merge conflicts in structured files (JSON, YAML, XML, TOML) are painful.
- Manual resolution is error-prone, time-consuming, and breaks CI/CD pipelines.
- `git-json-resolver` automates conflict handling with **configurable strategies**.
- Non-JSON formats are internally normalized to JSON → resolved → converted back.
## Features
- ⚡ **Primary focus on JSON** (first-class support)
- 🔄 **YAML, XML, TOML, JSON5** supported via conversion
- 🧩 **Rule-based strategies** with path/pattern matching
- 📁 **Multiple file parallel processing** with include/exclude patterns
- 🔌 **Pluggable matcher** abstraction (picomatch, micromatch, or custom)
- 🛠️ **CLI and programmatic API** support
- 📝 **Conflict sidecar files** for unresolved conflicts
- 🔄 **Backup and restore** functionality
- 📊 **Configurable logging** (memory or file-based)
- 🔀 **Git merge driver** support for seamless Git integration
- 🔧 **Plugin system** for custom strategies with JSON config support
## Installation
```bash
pnpm add git-json-resolver
```
**_or_**
```bash
npm install git-json-resolver
```
**_or_**
```bash
yarn add git-json-resolver
```
## Quick Start
### CLI Usage
```bash
# Initialize config file
npx git-json-resolver --init
# Run with default config
npx git-json-resolver
# Run with options
npx git-json-resolver --include "**/*.json" --debug --sidecar
# Restore from backups
npx git-json-resolver --restore .merge-backups
```
### Git Integration
Add a custom merge driver to your Git config:
```bash
git config merge.json-resolver.name "Custom JSON merge driver"
git config merge.json-resolver.driver "npx git-json-resolver %A %O %B"
```
Update `.gitattributes` to use it for JSON files:
```gitattributes
*.json merge=json-resolver
*.yaml merge=json-resolver
*.yml merge=json-resolver
*.toml merge=json-resolver
*.xml merge=json-resolver
```
**How it works:**
- Git automatically calls the merge driver during conflicts
- Uses same configuration and strategies as CLI mode
- Supports 3-way merge (ours, base, theirs)
- Returns proper exit codes (0 = success, 1 = conflicts)
## Configuration
### Programmatic API
```ts
import { resolveConflicts } from "git-json-resolver";
await resolveConflicts({
defaultStrategy: ["merge", "ours"],
rules: {
"dependencies.*": ["ours"],
version: ["theirs!"], // ! marks as important
"scripts.build": ["skip"],
},
include: ["**/*.json", "**/*.yaml"],
exclude: ["**/node_modules/**"],
matcher: "picomatch",
debug: true,
writeConflictSidecar: true,
backupDir: ".merge-backups",
});
```
### Config File
**JavaScript Config (`git-json-resolver.config.js`)**
```js
module.exports = {
defaultStrategy: ["merge", "ours"],
rules: {
// Exact path matching
"package.json": {
version: ["theirs!"],
dependencies: ["ours"],
},
// Pattern matching
"*.config.json": {
"*": ["merge"],
},
},
// Alternative: byStrategy format
byStrategy: {
ours: ["dependencies.*", "devDependencies.*"],
"theirs!": ["version", "name"],
},
include: ["**/*.json", "**/*.yaml", "**/*.yml"],
exclude: ["**/node_modules/**", "**/dist/**"],
matcher: "picomatch",
debug: false,
writeConflictSidecar: false,
loggerConfig: {
mode: "memory", // or "stream"
logDir: "logs",
levels: {
stdout: ["warn", "error"],
file: ["info", "warn", "error"],
},
},
};
```
**JSON Config (`git-json-resolver.config.json`) - ⚠️ Experimental**
```json
{
"$schema": "https://cdn.jsdelivr.net/npm/git-json-resolver@latest/schema/config.schema.json",
"defaultStrategy": ["merge", "ours"],
"plugins": ["my-plugin"],
"pluginConfig": {
"my-plugin": {
"option": "value"
}
},
"rules": {
"package.json": {
"version": ["semantic-version", "theirs"],
"dependencies": ["ours"]
}
},
"byStrategy": {
"ours": ["dependencies.*", "devDependencies.*"],
"theirs!": ["version", "name"]
}
}
```
**⚠️ JSON Config Limitations:**
- No TypeScript intellisense for plugin strategies
- Limited validation for custom strategy names
- No compile-time type checking
- **Recommended:** Use `.js` or `.ts` config for better developer experience
## Supported Strategies
- **merge** → deep merge objects/arrays where possible
- **ours** → take current branch value
- **theirs** → take incoming branch value
- **base** → revert to common ancestor
- **skip** → leave unresolved (creates conflict entry)
- **drop** → remove the field entirely
- **non-empty** → prefer non-empty value (ours > theirs > base)
- **update** → update with theirs if field exists in ours
- **concat** → concatenate arrays from both sides
- **unique** → merge arrays and remove duplicates
- **custom** → user-defined resolver functions
### Strategy Priority
- Strategies marked with `!` (important) are applied first
- Multiple strategies can be specified as fallbacks
- Custom strategies can be defined via `customStrategies` config
## Supported Formats
- **JSON** (native)
- **JSON5** → via `json5` peer dependency
- **YAML** → via `yaml` peer dependency
- **TOML** → via `smol-toml` peer dependency
- **XML** → via `fast-xml-parser` peer dependency
All non-JSON formats are converted to JSON → resolved → converted back to original format.
## CLI Options
```bash
# File patterns
--include "**/*.json,**/*.yaml" # Comma-separated patterns
--exclude "**/node_modules/**" # Exclusion patterns
# Matcher selection
--matcher picomatch # picomatch, micromatch, or custom
# Debug and logging
--debug # Enable verbose logging
--sidecar # Write conflict sidecar files
# Utilities
--init # Create starter config file
--restore .merge-backups # Restore from backup directory
```
## Architecture
- **Modular design**: Separate concerns (parsing, merging, serialization)
- **Reusable utilities**: Common merge logic extracted for maintainability
- **Optimized bundle**: Constants over enums for better minification
- **Comprehensive testing**: Full test coverage with vitest
- **Type-safe**: Full TypeScript support with proper type inference
## Advanced Features
### Pattern Matching
- **Exact paths**: `"package.json"`, `"src.config.database.host"`
- **Field matching**: `"[version]"` → matches any `version` field
- **Glob patterns**: `"dependencies.*"`, `"**.config.**"`
- **Wildcards**: `"*.json"`, `"src/**/*.config.js"`
### Plugin System
**For TypeScript/JavaScript configs, you can use either approach:**
#### 1. Direct Import (Recommended)
```ts
import { strategies } from "my-plugin";
// or import { semanticVersion, timestampLatest } from "my-plugin";
import { resolveConflicts } from "git-json-resolver";
await resolveConflicts({
customStrategies: {
...strategies,
// or "semantic-version": semanticVersion,
},
rules: {
version: ["semantic-version", "theirs"],
},
});
```
#### 2. Dynamic Loading
```ts
// Also works in .js/.ts configs
const config = {
plugins: ["my-plugin"],
pluginConfig: {
"my-plugin": { option: "value" },
},
rules: {
version: ["semantic-version", "theirs"],
},
};
```
#### 3. JSON Config (Dynamic Loading Only)
```json
{
"plugins": ["my-plugin"],
"pluginConfig": {
"my-plugin": { "option": "value" }
},
"rules": {
"version": ["semantic-version", "theirs"]
}
}
```
**⚠️ If plugin doesn't provide global types:**
```ts
import type { Config } from "git-json-resolver";
const config: Config<AllStrategies | "semantic-version" | "timestamp-latest"> = {
// ... your config
};
```
**Creating a Plugin**
```ts
import { StrategyPlugin, StrategyStatus, StrategyFn } from "git-json-resolver";
// Augment types for TypeScript support
declare module "git-json-resolver" {
interface PluginStrategies {
"semantic-version": string;
"timestamp-latest": string;
}
}
// Individual strategy functions (can be imported directly)
export const semanticVersion: StrategyFn = ({ ours, theirs }) => {
if (isNewerVersion(theirs, ours)) {
return { status: StrategyStatus.OK, value: theirs };
}
return { status: StrategyStatus.CONTINUE };
};
export const timestampLatest: StrategyFn = ({ ours, theirs }) => {
const oursTime = new Date(ours as string).getTime();
const theirsTime = new Date(theirs as string).getTime();
return {
status: StrategyStatus.OK,
value: oursTime > theirsTime ? ours : theirs,
};
};
// Export strategies object for direct import
export const strategies = {
"semantic-version": semanticVersion,
"timestamp-latest": timestampLatest,
};
// Plugin interface for dynamic loading (object-based)
const plugin: StrategyPlugin = {
strategies,
init: async config => {
console.log("Plugin initialized with:", config);
},
};
export default plugin;
// Alternative: Function-based plugin (NEW)
export default async function createPlugin(config?: any): Promise<StrategyPlugin> {
// Initialize with config if needed
console.log("Plugin initialized with:", config);
return {
strategies,
init: async initConfig => {
// Additional initialization if needed
},
};
}
```
### Custom Strategies (Inline)
```ts
import { StrategyStatus } from "git-json-resolver";
const config = {
customStrategies: {
"semantic-version": ({ ours, theirs }) => {
// Custom logic for semantic version resolution
if (isNewerVersion(theirs, ours)) {
return { status: StrategyStatus.OK, value: theirs };
}
return { status: StrategyStatus.CONTINUE };
},
},
rules: {
version: ["semantic-version", "theirs"],
},
};
```
### Logging & Debugging
- **Memory mode**: Fast, in-memory logging
- **Stream mode**: File-based logging for large operations
- **Per-file logs**: Separate log files for each processed file
- **Debug mode**: Detailed conflict information and strategy traces
## Plugin Development
See [PLUGIN_GUIDE.md](./PLUGIN_GUIDE.md) for detailed plugin development documentation.
## Contributing
Contributions welcome 🙌
- Fork, branch, PR — with tests (`vitest` required)
- Docs live in `libDocs/` (tutorials, guides, deep dives)
- API reference generated into `docs/` via TypeDoc
## License
This library is licensed under the MPL-2.0 open-source license.
> <img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 20px"/> Please enroll in [our courses](https://mayank-chaudhari.vercel.app/courses) or [sponsor](https://github.com/sponsors/mayank1513) our work.
<hr />
<p align="center" style="text-align:center">with 💖 by <a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a></p>