@kgdiff/cli 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -2
  3. package/dist/apply-command.d.ts +34 -0
  4. package/dist/apply-command.d.ts.map +1 -0
  5. package/dist/apply-command.js +137 -0
  6. package/dist/apply-command.js.map +1 -0
  7. package/dist/args.d.ts +96 -0
  8. package/dist/args.d.ts.map +1 -0
  9. package/dist/args.js +463 -0
  10. package/dist/args.js.map +1 -0
  11. package/dist/bin.d.ts +3 -0
  12. package/dist/bin.d.ts.map +1 -0
  13. package/dist/bin.js +17 -0
  14. package/dist/bin.js.map +1 -0
  15. package/dist/diff-command.d.ts +29 -0
  16. package/dist/diff-command.d.ts.map +1 -0
  17. package/dist/diff-command.js +236 -0
  18. package/dist/diff-command.js.map +1 -0
  19. package/dist/errors.d.ts +48 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +92 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/help.d.ts +11 -0
  24. package/dist/help.d.ts.map +1 -0
  25. package/dist/help.js +247 -0
  26. package/dist/help.js.map +1 -0
  27. package/dist/html.d.ts +53 -0
  28. package/dist/html.d.ts.map +1 -0
  29. package/dist/html.js +107 -0
  30. package/dist/html.js.map +1 -0
  31. package/dist/index.d.ts +25 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +24 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/inputs.d.ts +54 -0
  36. package/dist/inputs.d.ts.map +1 -0
  37. package/dist/inputs.js +209 -0
  38. package/dist/inputs.js.map +1 -0
  39. package/dist/io.d.ts +64 -0
  40. package/dist/io.d.ts.map +1 -0
  41. package/dist/io.js +2 -0
  42. package/dist/io.js.map +1 -0
  43. package/dist/locale.d.ts +58 -0
  44. package/dist/locale.d.ts.map +1 -0
  45. package/dist/locale.js +121 -0
  46. package/dist/locale.js.map +1 -0
  47. package/dist/main.d.ts +10 -0
  48. package/dist/main.d.ts.map +1 -0
  49. package/dist/main.js +51 -0
  50. package/dist/main.js.map +1 -0
  51. package/dist/markdown.d.ts +30 -0
  52. package/dist/markdown.d.ts.map +1 -0
  53. package/dist/markdown.js +93 -0
  54. package/dist/markdown.js.map +1 -0
  55. package/dist/model.d.ts +63 -0
  56. package/dist/model.d.ts.map +1 -0
  57. package/dist/model.js +151 -0
  58. package/dist/model.js.map +1 -0
  59. package/dist/node-io.d.ts +12 -0
  60. package/dist/node-io.d.ts.map +1 -0
  61. package/dist/node-io.js +135 -0
  62. package/dist/node-io.js.map +1 -0
  63. package/dist/output.d.ts +27 -0
  64. package/dist/output.d.ts.map +1 -0
  65. package/dist/output.js +50 -0
  66. package/dist/output.js.map +1 -0
  67. package/dist/overwrite.d.ts +14 -0
  68. package/dist/overwrite.d.ts.map +1 -0
  69. package/dist/overwrite.js +55 -0
  70. package/dist/overwrite.js.map +1 -0
  71. package/dist/report.d.ts +52 -0
  72. package/dist/report.d.ts.map +1 -0
  73. package/dist/report.js +64 -0
  74. package/dist/report.js.map +1 -0
  75. package/dist/style.d.ts +46 -0
  76. package/dist/style.d.ts.map +1 -0
  77. package/dist/style.js +140 -0
  78. package/dist/style.js.map +1 -0
  79. package/dist/summary.d.ts +40 -0
  80. package/dist/summary.d.ts.map +1 -0
  81. package/dist/summary.js +352 -0
  82. package/dist/summary.js.map +1 -0
  83. package/dist/version.d.ts +6 -0
  84. package/dist/version.d.ts.map +1 -0
  85. package/dist/version.js +19 -0
  86. package/dist/version.js.map +1 -0
  87. package/dist/width.d.ts +45 -0
  88. package/dist/width.d.ts.map +1 -0
  89. package/dist/width.js +232 -0
  90. package/dist/width.js.map +1 -0
  91. package/package.json +56 -3
  92. package/src/apply-command.ts +203 -0
  93. package/src/args.ts +605 -0
  94. package/src/bin.ts +22 -0
  95. package/src/diff-command.ts +299 -0
  96. package/src/errors.ts +131 -0
  97. package/src/help.ts +286 -0
  98. package/src/html.ts +145 -0
  99. package/src/index.ts +71 -0
  100. package/src/inputs.ts +269 -0
  101. package/src/io.ts +68 -0
  102. package/src/locale.ts +177 -0
  103. package/src/main.ts +56 -0
  104. package/src/markdown.ts +119 -0
  105. package/src/model.ts +181 -0
  106. package/src/node-io.ts +146 -0
  107. package/src/output.ts +81 -0
  108. package/src/overwrite.ts +75 -0
  109. package/src/report.ts +101 -0
  110. package/src/style.ts +164 -0
  111. package/src/summary.ts +440 -0
  112. package/src/version.ts +21 -0
  113. package/src/width.ts +222 -0
package/dist/width.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Terminal display width of text (East Asian Width, combining marks, emoji) and the helpers
3
+ * built on it: sanitizing untrusted labels, truncating with an ellipsis and padding.
4
+ *
5
+ * Widths follow what modern terminals render: CJK / fullwidth / emoji-presentation characters
6
+ * take two columns, combining marks, joiners and variation selectors none, East Asian
7
+ * "ambiguous" characters (`→`, `·`, `…`, box drawing) one. Measurement is per grapheme cluster
8
+ * (`Intl.Segmenter`), so `é` (e + U+0301), a ZWJ emoji sequence, a flag and a keycap
9
+ * are each measured as one unit.
10
+ */
11
+ /** East Asian Width W and F ranges (Unicode 16), as inclusive [start, end] pairs, sorted. */
12
+ const WIDE_RANGES = [
13
+ 0x1100, 0x115f, 0x231a, 0x231b, 0x2329, 0x232a, 0x23e9, 0x23ec, 0x23f0, 0x23f0, 0x23f3, 0x23f3,
14
+ 0x25fd, 0x25fe, 0x2614, 0x2615, 0x2630, 0x2637, 0x2648, 0x2653, 0x267f, 0x267f, 0x268a, 0x268f,
15
+ 0x2693, 0x2693, 0x26a1, 0x26a1, 0x26aa, 0x26ab, 0x26bd, 0x26be, 0x26c4, 0x26c5, 0x26ce, 0x26ce,
16
+ 0x26d4, 0x26d4, 0x26ea, 0x26ea, 0x26f2, 0x26f3, 0x26f5, 0x26f5, 0x26fa, 0x26fa, 0x26fd, 0x26fd,
17
+ 0x2705, 0x2705, 0x270a, 0x270b, 0x2728, 0x2728, 0x274c, 0x274c, 0x274e, 0x274e, 0x2753, 0x2755,
18
+ 0x2757, 0x2757, 0x2795, 0x2797, 0x27b0, 0x27b0, 0x27bf, 0x27bf, 0x2b1b, 0x2b1c, 0x2b50, 0x2b50,
19
+ 0x2b55, 0x2b55, 0x2e80, 0x2e99, 0x2e9b, 0x2ef3, 0x2f00, 0x2fd5, 0x2ff0, 0x303e, 0x3041, 0x3096,
20
+ 0x3099, 0x30ff, 0x3105, 0x312f, 0x3131, 0x318e, 0x3190, 0x31e5, 0x31ef, 0x321e, 0x3220, 0x3247,
21
+ 0x3250, 0xa48c, 0xa490, 0xa4c6, 0xa960, 0xa97c, 0xac00, 0xd7a3, 0xf900, 0xfaff, 0xfe10, 0xfe19,
22
+ 0xfe30, 0xfe52, 0xfe54, 0xfe66, 0xfe68, 0xfe6b, 0xff01, 0xff60, 0xffe0, 0xffe6, 0x16fe0, 0x16fe4,
23
+ 0x16ff0, 0x16ff1, 0x17000, 0x187f7, 0x18800, 0x18cd5, 0x18d00, 0x18d08, 0x1aff0, 0x1aff3, 0x1aff5,
24
+ 0x1affb, 0x1affd, 0x1affe, 0x1b000, 0x1b122, 0x1b132, 0x1b132, 0x1b150, 0x1b152, 0x1b155, 0x1b155,
25
+ 0x1b164, 0x1b167, 0x1b170, 0x1b2fb, 0x1d300, 0x1d356, 0x1d360, 0x1d376, 0x1f004, 0x1f004, 0x1f0cf,
26
+ 0x1f0cf, 0x1f18e, 0x1f18e, 0x1f191, 0x1f19a, 0x1f200, 0x1f202, 0x1f210, 0x1f23b, 0x1f240, 0x1f248,
27
+ 0x1f250, 0x1f251, 0x1f260, 0x1f265, 0x1f300, 0x1f320, 0x1f32d, 0x1f335, 0x1f337, 0x1f37c, 0x1f37e,
28
+ 0x1f393, 0x1f3a0, 0x1f3ca, 0x1f3cf, 0x1f3d3, 0x1f3e0, 0x1f3f0, 0x1f3f4, 0x1f3f4, 0x1f3f8, 0x1f43e,
29
+ 0x1f440, 0x1f440, 0x1f442, 0x1f4fc, 0x1f4ff, 0x1f53d, 0x1f54b, 0x1f54e, 0x1f550, 0x1f567, 0x1f57a,
30
+ 0x1f57a, 0x1f595, 0x1f596, 0x1f5a4, 0x1f5a4, 0x1f5fb, 0x1f64f, 0x1f680, 0x1f6c5, 0x1f6cc, 0x1f6cc,
31
+ 0x1f6d0, 0x1f6d2, 0x1f6d5, 0x1f6d7, 0x1f6dc, 0x1f6df, 0x1f6eb, 0x1f6ec, 0x1f6f4, 0x1f6fc, 0x1f7e0,
32
+ 0x1f7eb, 0x1f7f0, 0x1f7f0, 0x1f90c, 0x1f93a, 0x1f93c, 0x1f945, 0x1f947, 0x1f9ff, 0x1fa70, 0x1fa7c,
33
+ 0x1fa80, 0x1fa89, 0x1fa8f, 0x1fac6, 0x1face, 0x1fadc, 0x1fadf, 0x1fae9, 0x1faf0, 0x1faf8, 0x20000,
34
+ 0x2fffd, 0x30000, 0x3fffd,
35
+ ];
36
+ /** True when the code point is East Asian Wide or Fullwidth. O(log n). */
37
+ export function isWideCodePoint(cp) {
38
+ if (cp < 0x1100)
39
+ return false;
40
+ let lo = 0;
41
+ let hi = WIDE_RANGES.length / 2 - 1;
42
+ while (lo <= hi) {
43
+ const mid = (lo + hi) >> 1;
44
+ const start = WIDE_RANGES[mid * 2];
45
+ const end = WIDE_RANGES[mid * 2 + 1];
46
+ if (cp < start)
47
+ hi = mid - 1;
48
+ else if (cp > end)
49
+ lo = mid + 1;
50
+ else
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ const ZERO_WIDTH = /^[\p{Mn}\p{Me}\p{Cf}]$/u;
56
+ const EMOJI_PRESENTATION = /\p{Emoji_Presentation}/u;
57
+ const EMOJI = /\p{Emoji}/u;
58
+ const REGIONAL_INDICATOR = /\p{Regional_Indicator}/u;
59
+ /** Display width of one grapheme cluster: 0, 1 or 2 columns. */
60
+ export function graphemeWidth(grapheme) {
61
+ const cp = grapheme.codePointAt(0);
62
+ if (cp === undefined)
63
+ return 0;
64
+ if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0))
65
+ return 0;
66
+ if (cp < 0x7f && grapheme.length === 1)
67
+ return 1; // ASCII fast path
68
+ if (grapheme.length > 1) {
69
+ // Emoji sequences: presentation selector, ZWJ sequences, flags, keycaps, skin tones.
70
+ if (grapheme.includes('\uFE0F') && EMOJI.test(grapheme))
71
+ return 2;
72
+ if (grapheme.includes('\u200D') && EMOJI_PRESENTATION.test(grapheme))
73
+ return 2;
74
+ if (REGIONAL_INDICATOR.test(grapheme))
75
+ return 2;
76
+ }
77
+ const first = String.fromCodePoint(cp);
78
+ if (ZERO_WIDTH.test(first))
79
+ return 0;
80
+ if (grapheme.includes('\uFE0E'))
81
+ return 1; // text presentation selector
82
+ if (isWideCodePoint(cp) || EMOJI_PRESENTATION.test(first))
83
+ return 2;
84
+ return 1;
85
+ }
86
+ let segmenter;
87
+ /** Grapheme clusters of `text` (code points as a fallback when `Intl.Segmenter` is missing). */
88
+ export function graphemes(text) {
89
+ if (segmenter === undefined) {
90
+ segmenter =
91
+ typeof Intl.Segmenter === 'function'
92
+ ? new Intl.Segmenter('en', { granularity: 'grapheme' })
93
+ : null;
94
+ }
95
+ if (segmenter)
96
+ return Array.from(segmenter.segment(text), (s) => s.segment);
97
+ // Fallback: attach zero-width code points to the previous one.
98
+ const out = [];
99
+ for (const ch of text) {
100
+ const cp = ch.codePointAt(0);
101
+ if (out.length > 0 && (ZERO_WIDTH.test(ch) || (cp >= 0x1f3fb && cp <= 0x1f3ff))) {
102
+ out[out.length - 1] += ch;
103
+ }
104
+ else
105
+ out.push(ch);
106
+ }
107
+ return out;
108
+ }
109
+ const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
110
+ /** Display width of `text` in terminal columns. Text must not contain ANSI escape sequences. */
111
+ export function stringWidth(text) {
112
+ if (PRINTABLE_ASCII.test(text))
113
+ return text.length;
114
+ let width = 0;
115
+ for (const g of graphemes(text))
116
+ width += graphemeWidth(g);
117
+ return width;
118
+ }
119
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching control characters is the point.
120
+ const CONTROL = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g;
121
+ /**
122
+ * Invisible characters that can disguise text ("Trojan Source"): bidirectional controls
123
+ * (U+061C, U+200E/F, U+202A–U+202E, U+2066–U+2069), zero-width and invisible format characters
124
+ * (U+180E, U+200B, U+2060–U+2064, U+FEFF, U+FFF9–U+FFFB) and tag characters outside emoji flag
125
+ * sequences. Joiners (U+200C, U+200D) and variation selectors stay: scripts and emoji need them.
126
+ */
127
+ const INVISIBLE = /[\u061C\u180E\u200B\u200E\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF\uFFF9-\uFFFB]|(?<!\u{1F3F4}[\u{E0020}-\u{E007E}]*)[\u{E0000}-\u{E007F}]/gu;
128
+ /** Replace invisible and bidirectional control characters with a visible `⟨U+202E⟩`. */
129
+ export function revealInvisible(text) {
130
+ return text.replace(INVISIBLE, (ch) => {
131
+ const cp = ch.codePointAt(0);
132
+ return `⟨U+${cp.toString(16).toUpperCase().padStart(4, '0')}⟩`;
133
+ });
134
+ }
135
+ /**
136
+ * Make untrusted text (labels, ids, file names, parser messages) safe to print on one
137
+ * terminal line: tabs and line breaks become spaces, other C0 controls (notably ESC, which
138
+ * could inject escape sequences or hyperlinks) become control pictures (`␛`), C1 controls
139
+ * U+FFFD, and invisible or bidirectional controls a visible `⟨U+202E⟩`.
140
+ */
141
+ export function sanitize(text) {
142
+ return revealInvisible(text.replace(CONTROL, (ch) => {
143
+ const cp = ch.charCodeAt(0);
144
+ if (cp === 0x09 || cp === 0x0a || cp === 0x0d || cp === 0x2028 || cp === 0x2029)
145
+ return ' ';
146
+ if (cp < 0x20)
147
+ return String.fromCharCode(0x2400 + cp);
148
+ if (cp === 0x7f)
149
+ return '\u2421';
150
+ return '\uFFFD';
151
+ }));
152
+ }
153
+ /** `sanitize` line by line, keeping line breaks (stack traces). */
154
+ export function sanitizeLines(text) {
155
+ return text.split(/\r?\n/).map(sanitize).join('\n');
156
+ }
157
+ /**
158
+ * Truncate `text` to at most `max` columns, ending with `…` when something was cut. Never
159
+ * splits a grapheme; a wide character that does not fit is dropped whole (the result may then
160
+ * be one column narrower than `max`). Returns '' for `max` ≤ 0.
161
+ */
162
+ export function truncate(text, max, ellipsis = '…') {
163
+ if (max <= 0)
164
+ return '';
165
+ if (stringWidth(text) <= max)
166
+ return text;
167
+ const room = max - stringWidth(ellipsis);
168
+ if (room < 0)
169
+ return '';
170
+ let width = 0;
171
+ let out = '';
172
+ for (const g of graphemes(text)) {
173
+ const w = graphemeWidth(g);
174
+ if (width + w > room)
175
+ break;
176
+ out += g;
177
+ width += w;
178
+ }
179
+ return out.trimEnd() + ellipsis;
180
+ }
181
+ /** Pad `text` with spaces on the right to `width` columns (no-op when already wider). */
182
+ export function padEnd(text, width) {
183
+ const gap = width - stringWidth(text);
184
+ return gap > 0 ? text + ' '.repeat(gap) : text;
185
+ }
186
+ /** Pad `text` with spaces on the left to `width` columns (no-op when already wider). */
187
+ export function padStart(text, width) {
188
+ const gap = width - stringWidth(text);
189
+ return gap > 0 ? ' '.repeat(gap) + text : text;
190
+ }
191
+ /**
192
+ * Greedy word wrap to `width` columns. Breaks at spaces; CJK text (no spaces) breaks between
193
+ * graphemes. Lines never exceed `width` unless a single grapheme is wider.
194
+ */
195
+ export function wrap(text, width) {
196
+ if (width <= 0 || stringWidth(text) <= width)
197
+ return [text];
198
+ const lines = [];
199
+ let line = '';
200
+ let lineWidth = 0;
201
+ const flush = () => {
202
+ lines.push(line.trimEnd());
203
+ line = '';
204
+ lineWidth = 0;
205
+ };
206
+ for (const word of text.split(/(?<= )/)) {
207
+ const w = stringWidth(word);
208
+ if (lineWidth + stringWidth(word.trimEnd()) <= width) {
209
+ line += word;
210
+ lineWidth += w;
211
+ continue;
212
+ }
213
+ if (lineWidth > 0)
214
+ flush();
215
+ if (stringWidth(word.trimEnd()) <= width) {
216
+ line = word;
217
+ lineWidth = w;
218
+ continue;
219
+ }
220
+ for (const g of graphemes(word)) {
221
+ const gw = graphemeWidth(g);
222
+ if (lineWidth + gw > width && lineWidth > 0)
223
+ flush();
224
+ line += g;
225
+ lineWidth += gw;
226
+ }
227
+ }
228
+ if (line.trim())
229
+ lines.push(line.trimEnd());
230
+ return lines;
231
+ }
232
+ //# sourceMappingURL=width.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"width.js","sourceRoot":"","sources":["../src/width.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,6FAA6F;AAC7F,MAAM,WAAW,GAAsB;IACrC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC9F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAChG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACjG,OAAO,EAAE,OAAO,EAAE,OA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"}
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@kgdiff/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "git diff for knowledge graphs. Compare two graph files from the terminal, CI, or as a shareable HTML report.",
5
+ "keywords": [
6
+ "kgdiff",
7
+ "cli",
8
+ "diff",
9
+ "knowledge-graph",
10
+ "ontology",
11
+ "rdf"
12
+ ],
13
+ "homepage": "https://github.com/iiwish/kgdiff/tree/main/packages/cli#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/iiwish/kgdiff/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/iiwish/kgdiff.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "license": "MIT",
23
+ "author": "iiwish",
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "bin": {
33
+ "kgdiff": "./dist/bin.js"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "!src/**/*.test.ts"
39
+ ],
40
+ "engines": {
41
+ "node": ">=22"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org/"
46
+ },
47
+ "dependencies": {
48
+ "@kgdiff/core": "0.2.0",
49
+ "@kgdiff/formats": "0.2.0",
50
+ "@kgdiff/layout": "0.2.0",
51
+ "@kgdiff/scene": "0.2.0",
52
+ "@kgdiff/viewer": "0.2.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.build.json",
56
+ "typecheck": "tsc -p tsconfig.json",
57
+ "test": "vitest run"
58
+ }
6
59
  }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * `kgdiff apply <graph> <delta>`: apply a delta (docs/delta-format.md) and write the patched
3
+ * graph as kgdiff JSON.
4
+ */
5
+ import { basename } from 'node:path';
6
+ import {
7
+ applyDelta,
8
+ type Delta,
9
+ type EdgeIdentity,
10
+ type GraphJSON,
11
+ graphToJSON,
12
+ hashGraph,
13
+ type KGraph,
14
+ normalize,
15
+ } from '@kgdiff/core';
16
+ import { type ApplyCommandOptions, STDIO } from './args.js';
17
+ import { CliError, formatWarning, isKgdiffError } from './errors.js';
18
+ import { parseInput, type RawInput, readInput, readPair } from './inputs.js';
19
+ import type { CliIO } from './io.js';
20
+ import { formatInt } from './locale.js';
21
+ import { outputContext, writeOutputFile } from './output.js';
22
+ import { checkNoOverwrite } from './overwrite.js';
23
+ import { sanitize } from './width.js';
24
+
25
+ function isRecord(value: unknown): value is Record<string, unknown> {
26
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
27
+ }
28
+
29
+ /**
30
+ * Read a delta from JSON text. Accepts a `kgdiff.delta` document, or a `kgdiff.diff` document
31
+ * (`--json` output), whose delta is used. Version checks are left to `applyDelta`.
32
+ */
33
+ export function readDelta(input: RawInput): Delta {
34
+ const where = input.display === 'stdin' ? 'stdin' : `"${input.display}"`;
35
+ let value: unknown;
36
+ try {
37
+ value = JSON.parse(input.text);
38
+ } catch (error) {
39
+ throw new CliError(
40
+ 'input',
41
+ `${where} is not valid JSON: ${error instanceof Error ? error.message : String(error)}.`,
42
+ 'Create a delta with `kgdiff <before> <after> --delta <file>`.',
43
+ { cause: error },
44
+ );
45
+ }
46
+ if (isRecord(value) && value.format === 'kgdiff.diff' && isRecord(value.delta)) {
47
+ return value.delta as unknown as Delta;
48
+ }
49
+ if (!isRecord(value) || value.format !== 'kgdiff.delta') {
50
+ throw new CliError(
51
+ 'input',
52
+ `${where} is not a kgdiff delta (expected "format": "kgdiff.delta").`,
53
+ 'Create one with `kgdiff <before> <after> --delta <file>`. Is the order <graph> <delta>?',
54
+ );
55
+ }
56
+ return value as unknown as Delta;
57
+ }
58
+
59
+ /**
60
+ * The patched graph as kgdiff JSON (`graphToJSON`, which `kgdiff` reads back losslessly).
61
+ *
62
+ * Meta: `name` / `version` come from the delta's target (`applyDelta`); `source` names the
63
+ * output file (`source` argument), or is dropped when writing to stdout, so the result never
64
+ * claims to be the input file.
65
+ */
66
+ export function graphOutputJSON(graph: KGraph, source?: string): GraphJSON {
67
+ const { format, version, meta, nodes, edges } = graphToJSON(graph);
68
+ const { source: _inputSource, ...rest } = meta ?? {};
69
+ const outMeta = source !== undefined ? { ...rest, source } : rest;
70
+ return Object.keys(outMeta).length > 0
71
+ ? { format, version, meta: outMeta, nodes, edges }
72
+ : { format, version, nodes, edges };
73
+ }
74
+
75
+ const EDGE_IDENTITIES: readonly unknown[] = ['structure', 'id'];
76
+
77
+ export interface SkippedOp {
78
+ /** Index in `delta.ops`. */
79
+ readonly index: number;
80
+ readonly op: string;
81
+ readonly reason: string;
82
+ }
83
+
84
+ /**
85
+ * `--no-strict`: apply every operation whose precondition holds, in one pass, and collect the
86
+ * skipped ones (core's `onSkip`, in op order).
87
+ */
88
+ export function applyLeniently(
89
+ graph: KGraph,
90
+ delta: Delta,
91
+ ): { readonly result: KGraph; readonly skipped: readonly SkippedOp[] } {
92
+ const skipped: SkippedOp[] = [];
93
+ const result = applyDelta(graph, delta, {
94
+ strict: false,
95
+ onSkip: ({ index, op, reason }) => skipped.push({ index, op: String(op.op), reason }),
96
+ });
97
+ return { result, skipped };
98
+ }
99
+
100
+ const PARSE_OPTIONS_HINT =
101
+ 'If it is the same graph, parse it as when the delta was made: --format and --lang must match ' +
102
+ 'the options given to `kgdiff … --delta` (they change what the graph contains).';
103
+
104
+ /** Run the apply command. Returns the exit code. */
105
+ export async function runApply(o: ApplyCommandOptions, io: CliIO): Promise<number> {
106
+ const ctx = outputContext(io, o);
107
+ await checkNoOverwrite(
108
+ io,
109
+ [
110
+ { role: '<graph>', path: o.graph },
111
+ { role: '<delta>', path: o.delta },
112
+ ],
113
+ [{ role: '-o', path: o.output }],
114
+ );
115
+ const [graphInput, deltaInput] = await readPair(
116
+ readInput(io, o.graph, '<graph>', { allowEmpty: o.allowEmpty }),
117
+ readInput(io, o.delta, '<delta>', {
118
+ emptyHint: 'Create a delta with `kgdiff <before> <after> --delta <file>`.',
119
+ }),
120
+ );
121
+ const delta = readDelta(deltaInput);
122
+ const parsed = parseInput(graphInput, {
123
+ format: o.format,
124
+ formatFlag: '--format',
125
+ ...(o.languages ? { languages: o.languages } : {}),
126
+ });
127
+ const edgeIdentity = EDGE_IDENTITIES.includes(delta.edgeIdentity)
128
+ ? { edgeIdentity: delta.edgeIdentity as EdgeIdentity }
129
+ : {};
130
+ const graph = normalize(parsed.graph, edgeIdentity);
131
+
132
+ let result: KGraph;
133
+ let skipped: readonly SkippedOp[] = [];
134
+ if (o.strict) {
135
+ try {
136
+ result = applyDelta(graph, delta, { strict: true });
137
+ } catch (error) {
138
+ if (isKgdiffError(error) && error.code === 'delta-base-mismatch') {
139
+ const { expected, actual } = error.details ?? {};
140
+ const where = o.graph === STDIO ? '<graph> (stdin)' : `<graph> "${o.graph}"`;
141
+ throw new CliError(
142
+ 'input',
143
+ `${where} is not the base of this delta (graph ${String(actual)}, delta base ${String(expected)}).`,
144
+ `${PARSE_OPTIONS_HINT} Otherwise --no-strict applies what fits and reports the rest.`,
145
+ { cause: error },
146
+ );
147
+ }
148
+ if (isKgdiffError(error) && error.code === 'delta-conflict') {
149
+ throw new CliError(
150
+ 'input',
151
+ error.message,
152
+ `The graph differs from the delta's base. --no-strict applies the operations that fit and reports the others. ${PARSE_OPTIONS_HINT}`,
153
+ { cause: error },
154
+ );
155
+ }
156
+ throw error;
157
+ }
158
+ } else {
159
+ const lenient = applyLeniently(graph, delta);
160
+ result = lenient.result;
161
+ skipped = lenient.skipped;
162
+ if (skipped.length > 0) {
163
+ const total = delta.ops.length;
164
+ const count = formatInt(skipped.length);
165
+ ctx.notice(
166
+ formatWarning(
167
+ `Skipped ${count} of ${formatInt(total)} ${total === 1 ? 'operation' : 'operations'} that do not fit this graph${o.verbose ? ':' : ' (--verbose lists them).'}`,
168
+ ctx.errPaint,
169
+ ),
170
+ );
171
+ if (o.verbose) {
172
+ for (const s of skipped) {
173
+ ctx.notice(` #${s.index} ${sanitize(s.op)}: ${sanitize(s.reason)}\n`);
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ const hash = hashGraph(result);
180
+ const target = isRecord(delta.target) ? delta.target.hash : undefined;
181
+ if (typeof target === 'string' && hash !== target && skipped.length === 0) {
182
+ ctx.notice(
183
+ formatWarning(
184
+ `The result (${hash}) differs from the delta's target (${target}).`,
185
+ ctx.errPaint,
186
+ ),
187
+ );
188
+ }
189
+ const source = o.output === STDIO ? undefined : basename(o.output);
190
+ const text = `${JSON.stringify(graphOutputJSON(result, source), null, o.pretty ? 2 : undefined)}\n`;
191
+ if (o.output === STDIO) {
192
+ ctx.stdout(text);
193
+ } else {
194
+ const ops = delta.ops.length - skipped.length;
195
+ await writeOutputFile(
196
+ ctx,
197
+ o.output,
198
+ text,
199
+ `patched graph (${formatInt(ops)} ${ops === 1 ? 'operation' : 'operations'} applied, ${hash})`,
200
+ );
201
+ }
202
+ return 0;
203
+ }