Skip to content

Commit 4f81fa4

Browse files
committed
feat: Changed the diff display to be a structural diff
1 parent 21288cc commit 4f81fa4

3 files changed

Lines changed: 49 additions & 40 deletions

File tree

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"dependencies": {
77
"@codifycli/ink-form": "0.0.12",
8-
"@codifycli/schemas": "1.1.0-beta.10",
8+
"@codifycli/schemas": "1.2.0",
99
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
1010
"@mischnic/json-sourcemap": "^0.1.1",
1111
"@oclif/core": "^4.0.8",
@@ -43,7 +43,7 @@
4343
},
4444
"description": "Codify is a configuration-as-code tool that declaratively installs and manages developer tools and applications. Check out https://dashboard.codifycli.com for an editor.",
4545
"devDependencies": {
46-
"@codifycli/plugin-core": "^1.1.0-beta.25",
46+
"@codifycli/plugin-core": "^1.2.0",
4747
"@oclif/prettier-config": "^0.2.1",
4848
"@types/chalk": "^2.2.0",
4949
"@types/cors": "^2.8.19",
@@ -145,7 +145,7 @@
145145
"deploy": "npm run pkg && npm run notarize && npm run upload",
146146
"prepublishOnly": "npm run build"
147147
},
148-
"version": "1.1.1-beta.6",
148+
"version": "1.2.0-beta.1",
149149
"bugs": "https://github.com/codifycli/codify/issues",
150150
"keywords": [
151151
"oclif",

src/ui/plan-pretty-printer.ts

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import chalk from 'chalk';
2-
import * as Diff from 'diff';
32
import { ParameterOperation, PlanResponseData, ResourceOperation } from '@codifycli/schemas';
43

54
import { Plan, ResourcePlan } from '../entities/plan.js';
@@ -227,53 +226,63 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
227226
return typeof value === 'object' && value !== null && !Array.isArray(value);
228227
}
229228

230-
function formatObjectDiff(name: string, previousValue: object, newValue: object): string {
231-
const prevJson = JSON.stringify(previousValue, null, 2);
232-
const newJson = JSON.stringify(newValue, null, 2);
233-
const diff = Diff.diffLines(prevJson, newJson);
234-
235-
const coloredLines: Array<{ text: string; added: boolean; removed: boolean }> = [];
236-
for (const part of diff) {
237-
const lines = part.value.split('\n').filter((l) => l.length > 0);
238-
for (const line of lines) {
239-
// Skip the outer { } braces — we render those as the header/footer
240-
if (line === '{' || line === '}') continue;
241-
coloredLines.push({
242-
text: part.added ? chalk.green(line) : part.removed ? chalk.red(line) : line,
243-
added: part.added ?? false,
244-
removed: part.removed ?? false,
245-
});
229+
function formatObjectDiff(name: string, previousValue: Record<string, unknown>, newValue: Record<string, unknown>): string {
230+
const allKeys = Array.from(new Set([...Object.keys(previousValue), ...Object.keys(newValue)]));
231+
232+
type Entry = { op: 'noop' | 'add' | 'remove' | 'modify'; key: string; prev?: unknown; next?: unknown };
233+
const entries: Entry[] = allKeys.map((key) => {
234+
const inPrev = Object.hasOwn(previousValue, key);
235+
const inNext = Object.hasOwn(newValue, key);
236+
if (!inPrev) return { op: 'add', key, next: newValue[key] };
237+
if (!inNext) return { op: 'remove', key, prev: previousValue[key] };
238+
if (JSON.stringify(previousValue[key]) === JSON.stringify(newValue[key])) {
239+
return { op: 'noop', key, next: newValue[key] };
246240
}
247-
}
241+
return { op: 'modify', key, prev: previousValue[key], next: newValue[key] };
242+
});
248243

249244
const CONTEXT = 2;
250-
const included = new Set<number>();
251-
for (let i = 0; i < coloredLines.length; i++) {
252-
if (coloredLines[i].added || coloredLines[i].removed) {
253-
for (let j = Math.max(0, i - CONTEXT); j <= Math.min(coloredLines.length - 1, i + CONTEXT); j++) {
254-
included.add(j);
245+
const includedIndices = new Set<number>();
246+
for (let i = 0; i < entries.length; i++) {
247+
if (entries[i].op !== 'noop') {
248+
for (let j = Math.max(0, i - CONTEXT); j <= Math.min(entries.length - 1, i + CONTEXT); j++) {
249+
includedIndices.add(j);
255250
}
256251
}
257252
}
258253

259254
const resultLines: string[] = [`${chalk.yellow('~')} "${name}": {`];
260255
let lastIncluded = -1;
261256

262-
for (let i = 0; i < coloredLines.length; i++) {
263-
if (!included.has(i)) continue;
257+
for (let i = 0; i < entries.length; i++) {
258+
if (!includedIndices.has(i)) continue;
264259
if (lastIncluded !== -1 && i > lastIncluded + 1) {
265260
resultLines.push(' ...');
266261
}
267-
const { text, added, removed } = coloredLines[i];
268-
const symbol = added ? chalk.green('+') : removed ? chalk.red('-') : ' ';
269-
resultLines.push(` ${symbol} ${text}`);
270262
lastIncluded = i;
263+
const { op, key, prev, next } = entries[i];
264+
265+
if (op === 'noop') {
266+
resultLines.push(` "${key}": ${formatValue(next)},`);
267+
} else if (op === 'add') {
268+
resultLines.push(` ${chalk.green('+')} ${chalk.green(`"${key}": ${formatValue(next)},`)}`);
269+
} else if (op === 'remove') {
270+
resultLines.push(` ${chalk.red('-')} ${chalk.red(`"${key}": ${formatValue(prev)},`)}`);
271+
} else {
272+
resultLines.push(` ${chalk.yellow('~')} "${key}": ${formatValue(prev)} -> ${formatValue(next)},`);
273+
}
271274
}
272275

273276
resultLines.push(' },');
274277
return resultLines.join('\n');
275278
}
276279

280+
function formatValue(value: unknown): string {
281+
if (typeof value === 'string') return `"${value}"`;
282+
if (value === null || value === undefined) return String(value);
283+
return JSON.stringify(value);
284+
}
285+
277286
const OBJECT_SINGLE_SIDE_MAX_LINES = 20;
278287

279288
function formatObjectSingleSide(name: string, value: object, operation: ParameterOperation): string {

0 commit comments

Comments
 (0)