|
3 | 3 | import { readFileSync } from "node:fs";
|
4 | 4 | import * as consoleFormatter from "../lib/formatters/console.js";
|
5 | 5 | import * as jsonpatchFormatter from "../lib/formatters/jsonpatch.js";
|
6 |
| -import * as jsondiffpatch from "../lib/with-text-diffs.js"; |
| 6 | +import { create } from "../lib/with-text-diffs.js"; |
7 | 7 |
|
8 |
| -const allowedFlags = ["--help", "--format=json", "--format=jsonpatch"]; |
| 8 | +const allowedFlags = [ |
| 9 | + "--help", |
| 10 | + "--format", |
| 11 | + "--omit-removed-values", |
| 12 | + "--no-moves", |
| 13 | + "--no-text-diff", |
| 14 | + "--object-keys", |
| 15 | +]; |
9 | 16 |
|
10 | 17 | const args = process.argv.slice(2);
|
11 |
| -const flags = []; |
| 18 | +const flags = {}; |
12 | 19 | const files = [];
|
13 | 20 | for (const arg of args) {
|
14 | 21 | if (arg.startsWith("--")) {
|
15 |
| - if (allowedFlags.indexOf(arg) === -1) { |
16 |
| - console.error(`unrecognized option: ${arg}`); |
| 22 | + const argParts = arg.split("="); |
| 23 | + if (allowedFlags.indexOf(argParts[0]) === -1) { |
| 24 | + console.error(`unrecognized option: ${argParts[0]}`); |
17 | 25 | process.exit(2);
|
18 | 26 | }
|
19 |
| - flags.push(arg); |
| 27 | + flags[argParts[0]] = argParts[1] ?? true; |
20 | 28 | } else {
|
21 | 29 | files.push(arg);
|
22 | 30 | }
|
23 | 31 | }
|
24 | 32 |
|
25 |
| -const usage = |
26 |
| - "usage: jsondiffpatch left.json right.json" + |
27 |
| - "\n" + |
28 |
| - "\n note: http and https URLs are also supported\n"; |
| 33 | +const usage = () => { |
| 34 | + return `usage: jsondiffpatch left.json right.json |
| 35 | + note: http and https URLs are also supported |
| 36 | +
|
| 37 | +flags: |
| 38 | + --format=console (default) print a readable colorized diff |
| 39 | + --format=json output the pure JSON diff |
| 40 | + --format=json-compact pure JSON diff, no indentation |
| 41 | + --format=jsonpatch output JSONPatch (RFC 6902) |
| 42 | +
|
| 43 | + --omit-removed-values omits removed values from the diff |
| 44 | + --no-moves disable array moves detection |
| 45 | + --no-text-diff disable text diffs |
| 46 | + --object-keys=... (defaults to: id,key) optional comma-separated properties to match 2 objects between array versions (see objectHash) |
| 47 | +
|
| 48 | +example:`; |
| 49 | +}; |
| 50 | + |
| 51 | +function createInstance() { |
| 52 | + const format = |
| 53 | + typeof flags["--format"] === "string" ? flags["--format"] : "console"; |
| 54 | + const objectKeys = (flags["--object-keys="] ?? "id,key") |
| 55 | + .split(",") |
| 56 | + .map((key) => key.trim()); |
| 57 | + |
| 58 | + const jsondiffpatch = create({ |
| 59 | + objectHash: (obj, index) => { |
| 60 | + if (obj && typeof obj === "object") { |
| 61 | + for (const key of objectKeys) { |
| 62 | + if (key in obj) { |
| 63 | + return obj[key]; |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + return index; |
| 68 | + }, |
| 69 | + arrays: { |
| 70 | + detectMove: !flags["--no-moves"], |
| 71 | + }, |
| 72 | + omitRemovedValues: !!flags["--omit-removed-values"], |
| 73 | + textDiff: { |
| 74 | + ...(format === "jsonpatch" || !!flags["--no-text-diff"] |
| 75 | + ? { |
| 76 | + // text diff not supported by jsonpatch |
| 77 | + minLength: Number.MAX_VALUE, |
| 78 | + } |
| 79 | + : {}), |
| 80 | + }, |
| 81 | + }); |
| 82 | + return jsondiffpatch; |
| 83 | +} |
| 84 | + |
| 85 | +function printDiff(delta) { |
| 86 | + if (flags["--format"] === "json") { |
| 87 | + console.log(JSON.stringify(delta, null, 2)); |
| 88 | + } else if (flags["--format"] === "json-compact") { |
| 89 | + console.log(JSON.stringify(delta)); |
| 90 | + } else if (flags["--format"] === "jsonpatch") { |
| 91 | + jsonpatchFormatter.log(delta); |
| 92 | + } else { |
| 93 | + consoleFormatter.log(delta); |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +function getJson(path) { |
| 98 | + if (/^https?:\/\//i.test(path)) { |
| 99 | + // an absolute URL, fetch it |
| 100 | + return fetch(path).then((response) => response.json()); |
| 101 | + } |
| 102 | + return JSON.parse(readFileSync(path)); |
| 103 | +} |
| 104 | + |
| 105 | +const jsondiffpatch = createInstance(); |
29 | 106 |
|
30 | 107 | if (files.length !== 2 || flags.includes("--help")) {
|
31 |
| - console.log(usage); |
| 108 | + console.log(usage()); |
| 109 | + const delta = jsondiffpatch.diff( |
| 110 | + { |
| 111 | + property: "before", |
| 112 | + list: [{ id: 1 }, { id: 2 }, { id: 3, name: "item removed" }], |
| 113 | + longText: |
| 114 | + "when a text is very 🦕 long, diff-match-patch is used to create a text diff that only captures the changes, comparing each characther", |
| 115 | + }, |
| 116 | + { |
| 117 | + property: "after", |
| 118 | + newProperty: "added", |
| 119 | + list: [{ id: 2 }, { id: 1 }, { id: 4, name: "item added" }], |
| 120 | + longText: |
| 121 | + "when a text a bit long, diff-match-patch creates a text diff that captures the changes, comparing each characther", |
| 122 | + }, |
| 123 | + ); |
| 124 | + printDiff(delta); |
32 | 125 | } else {
|
33 | 126 | Promise.all([files[0], files[1]].map(getJson)).then(([left, right]) => {
|
34 | 127 | const delta = jsondiffpatch.diff(left, right);
|
35 | 128 | if (delta === undefined) {
|
36 | 129 | process.exit(0);
|
37 | 130 | } else {
|
38 |
| - if (flags.includes("--format=json")) { |
39 |
| - console.log(JSON.stringify(delta, null, 2)); |
40 |
| - } else if (flags.includes("--format=jsonpatch")) { |
41 |
| - jsonpatchFormatter.log(delta); |
42 |
| - } else { |
43 |
| - consoleFormatter.log(delta); |
44 |
| - } |
| 131 | + printDiff(delta); |
45 | 132 | // exit code 1 to be consistent with GNU diff
|
46 | 133 | process.exit(1);
|
47 | 134 | }
|
48 | 135 | });
|
49 | 136 | }
|
50 |
| - |
51 |
| -function getJson(path) { |
52 |
| - if (/^https?:\/\//i.test(path)) { |
53 |
| - // an absolute URL, fetch it |
54 |
| - return fetch(path).then((response) => response.json()); |
55 |
| - } |
56 |
| - return JSON.parse(readFileSync(path)); |
57 |
| -} |
|
0 commit comments