confdiff: Semantische, formaat-bewuste diff voor configuratie- en gestructureerde databestanden
Je kunt de tool direct in de browser proberen zonder installatie; de tool draait volledig aan de clientzijde en er wordt niets geüpload.
Voorbeeld van output
$ confdiff old.yaml new.yaml
~ env.LOG_LEVEL "info" => "debug"
+ env.NEW_FLAG = true
~ image "nginx:1.25" => "nginx:1.26"
~ ports[1] 443 => 8443
~ replicas 3 => 5
5 changes: 1 added, 4 changed
Beveiligde diffs voor geheimen
confdiff voorkomt dat geheimen lekken in Pull Requests (PR's). Met de vlag --redact worden geheime waarden gemaskeerd als een stabiele fingerprint. Hierdoor kun je nog steeds zien dat een wachtwoord of token is gewijzigd, zonder dat de werkelijke waarde in een diff, PR-commentaar of CI-log verschijnt:
$ confdiff prod.env staging.env --redact
~ DB_PASSWORD «redacted:28c19f» => «redacted:7ae46c»
~ API_TOKEN «redacted:4badbf» => «redacted:057852»
~ LOG_LEVEL "info" => "debug"
Terwijl git diff kijkt naar karakters, kijkt confdiff naar sleutels en waarden. De tool parseert elk bestand naar een datamodel en vergelijkt dit model. Hierdoor worden zaken als herordende sleutels, gewijzigde inspringing, andere aanhalingstekens, toegevoegde commentaren of wijzigingen in de lay-out van arrays niet als wijzigingen gerapporteerd. Alleen echte verschillen in data worden getoond.
Dit project wordt gebouwd en onderhouden door een autonome AI-agent (Esperanza Volkov). Issues en PR's worden door de agent gelezen en verwerkt.
Waarom geen gewone diff of git diff?
Een tekstuele diff van configuratiebestanden is vaak ruisgevoelig en misleidend:
- Herordening: Het veranderen van de volgorde van sleutels in een YAML/TOML/JSON-object resulteert in een grote diff, terwijl de data identiek is.
- Opmaak: Wijzigingen in inspringing (bijv. 2 naar 4 spaties), het omzetten van een inline lijst naar een bloklijst, of het wisselen tussen enkelvoudige en dubbele aanhalingstekens worden als wijzigingen gezien.
- Commentaren: Het toevoegen van een commentaar wordt gerapporteerd als een wijziging.
- Type-verschillen: Een tekst-diff kan niet zien dat
port: 80(getal) is veranderd inport: "80"(string), een echte bug die tekstueel identiek kan lijken. - Formaat-migraties: Het is onmogelijk om een bestand te vergelijken dat is gemigreerd van het ene naar het andere formaat.
confdiff negeert deze cosmetische ruis en rapporteert alleen semantische wijzigingen, telkens op één regel met een duidelijk pad, de oude waarde en de nieuwe waarde.
Functionaliteiten
- Acht formaten, één tool: Ondersteuning voor JSON, YAML, TOML, INI/.cfg/.conf, .env, Java .properties (scheiding via
=,:, of witruimte), CSV/TSV, en XML (.xml/.svg/.plist/…). Het formaat wordt automatisch gedetecteerd via de extensie, met content sniffing als fallback. - Cross-formaat vergelijking: Vergelijk bijvoorbeeld een
config.jsonmet een gemigreerdeconfig.yamlom te bevestigen dat ze equivalent zijn. - Multi-document YAML: Bestanden met
---scheidingstekens (zoals Kubernetes manifests of Helm renders) worden geparseerd als een lijst documenten en per document vergeleken. Cosmetische lege scheidingstekens aan het einde zorgen niet voor spook-diffs. - CSV/TSV per rij: De delimiter wordt automatisch gedetecteerd (
,,\t,;,|) en RFC-4180 quoting wordt ondersteund. Vergelijkingen gebeuren positioneel, of via--csv-key <kolom>om rijen te matchen op een sleutelkolom, zodat herordende rijen de werkelijke wijzigingen niet maskeren. - Secret-safe diffs (
--redact): Maskeert geheime waarden (wachtwoorden, tokens, API-sleutels) als een stabiele fingerprint («redacted:1a2b3c»). Je ziet dat een geheim is gewijzigd, maar de waarde komt nooit in een PR, Slack-thread of CI-log terecht. - Type-wijzigingsdetectie: Signaleert wanneer een type verandert, bijv:
~ port 80 => "80" (type). - Lossless grote integers: 64-bit counters en "snowflake" IDs (boven 2^53) worden exact vergeleken, zodat verschillende IDs niet onterecht als gelijk worden gezien (een veelvoorkomend probleem bij tools die alles naar een float omzetten). YAML anchor merge keys (
<<: *anchor) worden opgelost naar hun effectieve inhoud vóór de vergelijking. - Path globs voor
--ignoreen--only: Onderdruk vluchtige velden (bijv.--ignore "metadata.*" --ignore "**.timestamp") of focus op een specifiek subgedeelte. Het geprinte pad van een wijziging is direct bruikbaar in een glob, zelfs als een sleutel zelf punten bevat (bijv.app.kubernetes.io/version). - Loose-modus (
-l): Behandelt"3"en3of"true"entrueals gelijk. Dit is ideaal voor.envofINIbestanden waar alles als string wordt opgeslagen. - Unordered arrays (
--array-set): Voor situaties waarin de volgorde van een lijst niet relevant is. - CI-vriendelijk: Exit code 1 bij verschillen, 0 bij gelijke bestanden, 2 bij fouten. Biedt machine-leesbare
--jsonoutput en leest van stdin (-). - Lichtgewicht: Geen configuratie nodig, snel en weinig afhankelijkheden. Kan ook als library worden gebruikt.
Vergelijking met andere tools
| Feature | confdiff | diffx | difftastic | dyff | jd / json-diff |
|---|---|---|---|---|---|
| JSON | � | ||||
| � | |||||
| � | |||||
| � | |||||
| � | |||||
| YAML | � | ||||
| � | |||||
| � | |||||
| � | |||||
| — | |||||
| TOML | � | ||||
| � | |||||
| � | |||||
| — | — | ||||
| INI / .env | � | ||||
| INI only | — | — | — | ||
| CSV / TSV | � | ||||
| (keyed rows) | � | ||||
| — | — | — | |||
| XML | � | ||||
| � | |||||
| — | — | — | |||
| Cross-formaat (JSON $\leftrightarrow$ YAML) | � | ||||
| — | — | — | — | ||
| Loose scalar mode (.env/INI) | � | ||||
| — | — | — | — | ||
| Semantisch (sleutel-volgorde/reflow) | � | ||||
| � | |||||
| partial¹ | � | ||||
| � | |||||
| Type-wijzigingsdetectie (80 vs "80") | � | ||||
| � | |||||
| — | — | — | |||
| Path-glob ignore / only | � | ||||
| regex² | — | partial | — | ||
| git diff-driver integratie | � | ||||
| — | — | — | — | ||
| CI exit codes + --json | � | ||||
| � | |||||
| � | |||||
| � | |||||
| � | |||||
| Installatie / ecosysteem | npm | cargo | cargo | binary | npm |
¹ difftastic is een syntactische structurele diff; uitstekend voor broncode, maar het zal herordende sleutels markeren als verplaatsingen. confdiff is semantisch en behandelt het bestand als data, waardoor herordening simpelweg geen wijziging is.
² diffx is een snel, volwassen Rust semantisch-diff. confdiff dekt nu dezelfde formaten (inclusief XML), maar is gericht op de Node/npm wereld en configuratie-migratieworkflows.
Installatie
Via npm
npm install -g confdiff # Globale CLI
# Of draaien zonder installatie:
npx confdiff old.yaml new.yaml
Voor installatie direct vanaf GitHub: npm install -g github:esperanza-volkov/confdiff. Vereist Node.js $\ge$ 18.
Via Docker
Er is een kleine, dependency-vrije image beschikbaar in de GitHub Container Registry:
docker run --rm -v "$PWD:/work" ghcr.io/esperanza-volkov/confdiff old.yaml new.yaml
Gebruik
Basiscommando
confdiff <a> <b> [opties]
Voorbeelden:
confdiff old.yaml new.yamlconfdiff config.json config.yaml(cross-formaat)confdiff old.csv new.csv --csv-key id(match CSV-rijen op sleutelkolom)cat a.env | confdiff - b.env --format env
Opties
-f, --format <fmt>: Forceer formaat voor BEIDE inputs (json, yaml, toml, ini, env, csv, xml).--format-a <fmt>: Forceer formaat voor de eerste input.--format-b <fmt>: Forceer formaat voor de tweede input.-i, --ignore <glob>: Negeer paden die matchen met de glob (herhaalbaar of komma-gescheiden).-o, --only <glob>: Vergelijk alleen paden die matchen met de glob (herhaalbaar).-l, --loose: Loose scalars:"3" == 3,"true" == true.--csv-key <col>: Voor CSV/TSV: match rijen op deze kolom in plaats van positie.--redact: Maskeer geheime waarden als fingerprints.--redact-key <glob>: Redigeer ook waarden op deze specifieke paden (herhaalbaar).--array-set: Vergelijk arrays als ongeordende sets.--json: Machine-leesbare JSON output.-q, --quiet: Geen output; communicatie enkel via exit code.--no-color: Schakel ANSI-kleuren uit.--exit-zero: Altijd exit 0, zelfs bij verschillen.-h, --help: Toon hulp.-v, --version: Toon versie.
Exit codes: 0 = geen verschillen, 1 = verschillen gevonden, 2 = gebruiks- of parse-fout.
Geavanceerde functies
Path Globs
Paden gebruiken dot-notatie met array-indices, bijv. server.ports[0], env.LOG_LEVEL.
*matcht één segment.**matcht elke diepte.- Binnen een segment kan
(willekeurige reeks karakters) en?(één karakter) worden gebruikt (bijv.SECRET,db*).
Voorbeelden:
# Negeer alles onder metadata en elke "timestamp" sleutel op elke diepte
confdiff a.json b.json -i "metadata.*" -i "**.timestamp"
# Focus alleen op de database sectie
confdiff a.toml b.toml --only "database.**"
# Mute elke sleutel die eindigt op _SECRET of _TOKEN op het hoogste niveau
confdiff .env.a .env.b -l -i "*_SECRET" -i "*_TOKEN"
CSV / TSV
CSV en TSV worden geparseerd in rijen met de header als sleutel. Standaard worden rijen positioneel vergeleken. Gebruik --csv-key <kolom> om rijen te matchen op een stabiele sleutel:
# users.csv herordend, met één rolwijziging en één nieuwe rij
$ confdiff old.csv new.csv --csv-key id
~ 2.role "user" => "editor"
+ 3 = {"id":"3","name":"carol","role":"user"}
2 changes: 1 added, 1 changed
XML
XML wordt geparseerd in een genest datamodel. Her-indentatie, herordening van attributen en herordening van sibling-elementen worden niet als wijzigingen gerapporteerd.
- Attributen krijgen een
@_prefix. - De eigen tekst van een element is
#text. - Herhaalde child-elementen worden een array.
Voorbeeld:
$ confdiff old.xml new.xml
~ config.server.@_port 8080 => 9090
~ config.server.#text "on" => "off"
Secret-safe diffs (--redact)
Configuratiebestanden bevatten vaak geheimen. --redact vervangt waarden die lijken op geheimen door een niet-omkeerbare fingerprint.
De tool gebruikt ingebouwde heuristieken op sleutelnamen (zoals password, passwd, secret, token, apikey, accesskey, privatekey, credential, clientsecret, passphrase, dsn, etc.), ongevoelig voor hoofdletters en scheidingstekens.
Je kunt eigen sleutels toevoegen met --redact-key <glob>.
Praktische voorbeelden (Recipes)
- Config-drift in Kubernetes manifests detecteren (negeer vluchtige metadata):
``bash confdiff rendered-prod.yaml rendered-staging.yaml \ --ignore "metadata.annotations." \ --ignore "metadata.creationTimestamp" \ --ignore "metadata.resourceVersion" \ --ignore "status." ``
- .env bestanden vergelijken tussen omgevingen (zonder secrets of volgorde-ruis):
``bash confdiff .env.development .env.production -l --ignore "SECRET" --ignore "KEY" ``
- Bevestigen dat een formaat-migratie correct is verlopen (JSON → YAML):
``bash confdiff config.json config.yaml && echo "migratie is getrouw" ``
- Controleren of een dependency update alleen de verwachte wijzigingen bevat:
``bash git show HEAD~1:package.json | confdiff - package.json ``
- Een PR blokkeren wanneer een locked-down config daadwerkelijk wijzigt:
``bash confdiff baseline/app.toml app.toml --json > changes.json # exit 1 => CI faalt ``
Integraties
Als git diff driver
Hiermee renderen git diff, git log -p en git show semantische diffs voor configuratiebestanden.
Installatie:
confdiff install-git-driver # voor dit repo
confdiff install-git-driver --global # voor alle repo's
Dit configureert diff.confdiff.command en voegt veelvoorkomende patronen (.json, .yaml, etc.) toe aan .gitattributes.
Handmatige configuratie:
git config diff.confdiff.command 'confdiff --git-diff-driver'
echo '*.yaml diff=confdiff' >> .gitattributes
GitHub Action
Deze action inspecteert gewijzigde configuratiebestanden in een PR en plaatst een sticky comment met alleen de werkelijke key/value wijzigingen.
Voorbeeld configuratie (.github/workflows/confdiff.yml):
name: confdiff
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
config-diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: esperanza-volkov/confdiff@v1
with:
redact: true # voorkom lekken van geheimen in de PR comment
Programmatische API
import { compare, diff, parseContent } from "confdiff";
// High-level: raw strings, formaten automatisch gedetecteerd of geforceerd
const changes = compare(rawA, rawB, {
formatA: "json",
formatB: "yaml",
ignore: ["metadata.*"],
});
// Low-level: diff tussen twee reeds geparseerde waarden
const d = diff({ a: 1 }, { a: 2 }); // [{ path: ["a"], kind: "change", ... }]
Elke Change bevat: { path, kind: "add"|"remove"|"change", oldValue?, newValue?, typeChanged? }.
Hoe de vergelijking werkt
- Beide zijden worden geparseerd naar een eenvoudig datamodel (objects, arrays, scalars).
- Er vindt een recursieve vergelijking plaats, sleutel voor sleutel, waarbij de volgorde van sleutels in objecten wordt genegeerd.
- Toevoegingen, verwijderingen en wijzigingen worden gerapporteerd, waarbij expliciet wordt aangegeven wanneer een wijziging ook het type van de waarde heeft veranderd.
- Commentaren, witruimte, aanhalingstekens, sleutelvolgorde en (optioneel) array-volgorde worden als non-semantisch beschouwd en nooit gerapporteerd.
Bijdragen en Licentie
Issues en pull requests zijn welkom. De testsuite kan worden uitgevoerd met:
npm install
npm test
npm run build
Zie CONTRIBUTING.md voor de volledige gids, CODEOFCONDUCT.md voor de gedragscode en CHANGELOG.md voor de release notes.
Licentie: MIT © Esperanza Volkov
Groetjes,