difit 2.2.5 → 2.2.7

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.
@@ -129,6 +129,7 @@ describe('GitDiffParser', () => {
129
129
  additions: 0,
130
130
  deletions: 0,
131
131
  chunks: [], // Binary files should have empty chunks
132
+ isGenerated: false,
132
133
  });
133
134
  });
134
135
  it('parses deleted binary file correctly', () => {
@@ -154,6 +155,7 @@ describe('GitDiffParser', () => {
154
155
  additions: 0,
155
156
  deletions: 0,
156
157
  chunks: [],
158
+ isGenerated: false,
157
159
  });
158
160
  });
159
161
  it('parses modified binary file correctly', () => {
@@ -178,6 +180,7 @@ describe('GitDiffParser', () => {
178
180
  additions: 0,
179
181
  deletions: 0,
180
182
  chunks: [],
183
+ isGenerated: false,
181
184
  });
182
185
  });
183
186
  it('parses renamed binary file correctly', () => {
@@ -202,6 +205,7 @@ describe('GitDiffParser', () => {
202
205
  additions: 0,
203
206
  deletions: 0,
204
207
  chunks: [],
208
+ isGenerated: false,
205
209
  });
206
210
  });
207
211
  it('handles non-binary files normally', () => {
@@ -229,6 +233,7 @@ describe('GitDiffParser', () => {
229
233
  additions: 1,
230
234
  deletions: 0,
231
235
  chunks: expect.any(Array), // Should have parsed chunks
236
+ isGenerated: false,
232
237
  });
233
238
  // Verify chunks were parsed
234
239
  expect(result.chunks).toHaveLength(1);
@@ -259,6 +264,7 @@ describe('GitDiffParser', () => {
259
264
  additions: 0,
260
265
  deletions: 1,
261
266
  chunks: expect.any(Array),
267
+ isGenerated: false,
262
268
  });
263
269
  });
264
270
  it('detects added files using /dev/null indicator', () => {
@@ -279,6 +285,7 @@ describe('GitDiffParser', () => {
279
285
  };
280
286
  const result = parser.parseFileBlock(diffLines.join('\n'), summary);
281
287
  expect(result.status).toBe('added');
288
+ expect(result.isGenerated).toBe(false);
282
289
  });
283
290
  it('detects deleted files using /dev/null indicator', () => {
284
291
  const diffLines = [
@@ -298,6 +305,7 @@ describe('GitDiffParser', () => {
298
305
  };
299
306
  const result = parser.parseFileBlock(diffLines.join('\n'), summary);
300
307
  expect(result.status).toBe('deleted');
308
+ expect(result.isGenerated).toBe(false);
301
309
  });
302
310
  });
303
311
  describe('parseFileBlock with quoted paths', () => {
@@ -317,6 +325,7 @@ describe('GitDiffParser', () => {
317
325
  expect(result.status).toBe('added');
318
326
  expect(result.additions).toBe(1);
319
327
  expect(result.deletions).toBe(0);
328
+ expect(result.isGenerated).toBe(false);
320
329
  });
321
330
  it('parses summary-provided filenames with escaped spaces', () => {
322
331
  const diffLines = [
@@ -337,6 +346,7 @@ describe('GitDiffParser', () => {
337
346
  const result = parser.parseFileBlock(diffLines.join('\n'), summary);
338
347
  expect(result?.path).toBe('templates/test file.py');
339
348
  expect(result?.oldPath).toBeUndefined();
349
+ expect(result?.isGenerated).toBe(false);
340
350
  });
341
351
  it('parses file paths with Jinja template brackets correctly', () => {
342
352
  const diffLines = [
@@ -352,6 +362,7 @@ describe('GitDiffParser', () => {
352
362
  expect(result).toBeDefined();
353
363
  expect(result.path).toBe('templates/test_000_{{ package_name }}/__.py');
354
364
  expect(result.status).toBe('added');
365
+ expect(result.isGenerated).toBe(false);
355
366
  });
356
367
  it('parses file paths with escaped characters correctly', () => {
357
368
  const diffLines = [
@@ -367,6 +378,7 @@ describe('GitDiffParser', () => {
367
378
  expect(result).toBeDefined();
368
379
  expect(result.path).toBe('file\twith\ttabs.txt');
369
380
  expect(result.status).toBe('added');
381
+ expect(result.isGenerated).toBe(false);
370
382
  });
371
383
  it('parses renamed files with spaces correctly', () => {
372
384
  const diffLines = [
@@ -380,6 +392,7 @@ describe('GitDiffParser', () => {
380
392
  expect(result.path).toBe('new folder/new name.txt');
381
393
  expect(result.oldPath).toBe('old folder/old name.txt');
382
394
  expect(result.status).toBe('renamed');
395
+ expect(result.isGenerated).toBe(false);
383
396
  });
384
397
  it('still handles unquoted paths correctly', () => {
385
398
  const diffLines = [
@@ -399,6 +412,7 @@ describe('GitDiffParser', () => {
399
412
  expect(result.status).toBe('modified');
400
413
  expect(result.additions).toBe(1);
401
414
  expect(result.deletions).toBe(1);
415
+ expect(result.isGenerated).toBe(false);
402
416
  });
403
417
  it('handles unquoted paths with spaces when core.quotePath=false', () => {
404
418
  const diffLines = [
@@ -416,6 +430,7 @@ describe('GitDiffParser', () => {
416
430
  expect(result.status).toBe('modified');
417
431
  expect(result.additions).toBe(1);
418
432
  expect(result.deletions).toBe(1);
433
+ expect(result.isGenerated).toBe(false);
419
434
  });
420
435
  it('decodes unquoted octal escapes in diff headers', () => {
421
436
  const diffLines = [
@@ -430,6 +445,7 @@ describe('GitDiffParser', () => {
430
445
  const result = parser.parseFileBlock(diffLines.join('\n'), null);
431
446
  expect(result?.path).toBe('some folder/file name.ts');
432
447
  expect(result?.oldPath).toBeUndefined();
448
+ expect(result?.isGenerated).toBe(false);
433
449
  });
434
450
  it('handles unquoted paths containing "b/" in filename', () => {
435
451
  const diffLines = [
@@ -446,6 +462,7 @@ describe('GitDiffParser', () => {
446
462
  expect(result.path).toBe('dir b/sub/file');
447
463
  expect(result.oldPath).toBeUndefined();
448
464
  expect(result.status).toBe('modified');
465
+ expect(result.isGenerated).toBe(false);
449
466
  });
450
467
  it('handles renamed files with "b/" in the path', () => {
451
468
  const diffLines = [
@@ -459,6 +476,7 @@ describe('GitDiffParser', () => {
459
476
  expect(result.path).toBe('new b/path/file');
460
477
  expect(result.oldPath).toBe('old b/path/file');
461
478
  expect(result.status).toBe('renamed');
479
+ expect(result.isGenerated).toBe(false);
462
480
  });
463
481
  it('handles alternative git diff prefixes for working tree comparisons', () => {
464
482
  const diffLines = [
@@ -481,6 +499,7 @@ describe('GitDiffParser', () => {
481
499
  expect(result.path).toBe('a/test.txt');
482
500
  expect(result.oldPath).toBeUndefined();
483
501
  expect(result.status).toBe('modified');
502
+ expect(result.isGenerated).toBe(false);
484
503
  });
485
504
  it('handles rename metadata with alternative git prefixes', () => {
486
505
  const diffLines = [
@@ -501,6 +520,7 @@ describe('GitDiffParser', () => {
501
520
  expect(result.path).toBe('new/name.txt');
502
521
  expect(result.oldPath).toBe('old/name.txt');
503
522
  expect(result.status).toBe('renamed');
523
+ expect(result.isGenerated).toBe(false);
504
524
  });
505
525
  it('ignores trailing metadata separators in diff path lines', () => {
506
526
  const diffLines = [
@@ -522,6 +542,7 @@ describe('GitDiffParser', () => {
522
542
  expect(result).toBeDefined();
523
543
  expect(result.path).toBe('foo bar.txt');
524
544
  expect(result.status).toBe('modified');
545
+ expect(result.isGenerated).toBe(false);
525
546
  });
526
547
  it('prefers header paths over summary paths when they differ', () => {
527
548
  const diffLines = [
@@ -543,6 +564,7 @@ describe('GitDiffParser', () => {
543
564
  expect(result).toBeDefined();
544
565
  expect(result?.path).toBe('a/test.txt');
545
566
  expect(result?.status).toBe('added');
567
+ expect(result?.isGenerated).toBe(false);
546
568
  });
547
569
  it('does not treat added files as renamed even if summary includes from path', () => {
548
570
  const diffLines = [
@@ -565,6 +587,7 @@ describe('GitDiffParser', () => {
565
587
  expect(result).toBeDefined();
566
588
  expect(result?.status).toBe('added');
567
589
  expect(result?.oldPath).toBeUndefined();
590
+ expect(result?.isGenerated).toBe(false);
568
591
  });
569
592
  it('parses file paths with octal escape sequences correctly', () => {
570
593
  const diffLines = [
@@ -580,6 +603,7 @@ describe('GitDiffParser', () => {
580
603
  expect(result).toBeDefined();
581
604
  expect(result.path).toBe('fileä.txt');
582
605
  expect(result.status).toBe('added');
606
+ expect(result.isGenerated).toBe(false);
583
607
  });
584
608
  it('parses file paths with mixed escape sequences correctly', () => {
585
609
  const diffLines = [
@@ -595,6 +619,7 @@ describe('GitDiffParser', () => {
595
619
  expect(result).toBeDefined();
596
620
  expect(result.path).toBe('diré/file\twith\nmixed.txt');
597
621
  expect(result.status).toBe('added');
622
+ expect(result.isGenerated).toBe(false);
598
623
  });
599
624
  });
600
625
  describe('File status detection improvements', () => {
@@ -614,6 +639,7 @@ describe('GitDiffParser', () => {
614
639
  };
615
640
  const result = parser.parseFileBlock(diffLines.join('\n'), summary);
616
641
  expect(result.status).toBe('added');
642
+ expect(result.isGenerated).toBe(false);
617
643
  });
618
644
  it('prioritizes deleted file mode over other indicators', () => {
619
645
  const diffLines = [
@@ -631,6 +657,7 @@ describe('GitDiffParser', () => {
631
657
  };
632
658
  const result = parser.parseFileBlock(diffLines.join('\n'), summary);
633
659
  expect(result.status).toBe('deleted');
660
+ expect(result.isGenerated).toBe(false);
634
661
  });
635
662
  });
636
663
  describe('parseStdinDiff', () => {
@@ -655,6 +682,7 @@ index abc123..def456 100644
655
682
  additions: 1,
656
683
  deletions: 1,
657
684
  chunks: expect.any(Array),
685
+ isGenerated: false,
658
686
  },
659
687
  ],
660
688
  });
@@ -685,12 +713,14 @@ index 0000000..1234567
685
713
  status: 'modified',
686
714
  additions: 1,
687
715
  deletions: 1,
716
+ isGenerated: false,
688
717
  });
689
718
  expect(result.files[1]).toMatchObject({
690
719
  path: 'file2.js',
691
720
  status: 'added',
692
721
  additions: 3,
693
722
  deletions: 0,
723
+ isGenerated: false,
694
724
  });
695
725
  });
696
726
  it('should handle deleted files', async () => {
@@ -708,6 +738,7 @@ index abc123..0000000
708
738
  status: 'deleted',
709
739
  additions: 0,
710
740
  deletions: 2,
741
+ isGenerated: false,
711
742
  });
712
743
  });
713
744
  it('should handle renamed files', async () => {
@@ -722,6 +753,7 @@ rename to new-name.txt`;
722
753
  status: 'renamed',
723
754
  additions: 0,
724
755
  deletions: 0,
756
+ isGenerated: false,
725
757
  });
726
758
  });
727
759
  it('should handle empty diff', async () => {
@@ -750,6 +782,7 @@ index abc123..def456 100644
750
782
  expect(result.files[0]).toMatchObject({
751
783
  additions: 3,
752
784
  deletions: 2,
785
+ isGenerated: false,
753
786
  });
754
787
  });
755
788
  it('should handle diffs with multiple chunks', async () => {
@@ -772,6 +805,7 @@ index abc123..def456 100644
772
805
  expect(result.files[0]).toMatchObject({
773
806
  additions: 2,
774
807
  deletions: 1,
808
+ isGenerated: false,
775
809
  });
776
810
  });
777
811
  it('should handle binary files', async () => {
@@ -786,6 +820,7 @@ Binary files /dev/null and b/image.png differ`;
786
820
  additions: 0,
787
821
  deletions: 0,
788
822
  chunks: [],
823
+ isGenerated: false,
789
824
  });
790
825
  });
791
826
  it('should handle diffs with context lines', async () => {
@@ -842,4 +877,166 @@ index abc123..def456 100644
842
877
  expect(result).toEqual({ additions: 2, deletions: 1 });
843
878
  });
844
879
  });
880
+ describe('Generated file detection', () => {
881
+ const lockFiles = [
882
+ 'package-lock.json',
883
+ 'yarn.lock',
884
+ 'pnpm-lock.yaml',
885
+ 'Cargo.lock',
886
+ 'Gemfile.lock',
887
+ 'poetry.lock',
888
+ 'composer.lock',
889
+ 'Pipfile.lock',
890
+ 'go.sum',
891
+ 'go.mod',
892
+ 'pubspec.lock',
893
+ 'flake.lock',
894
+ ];
895
+ it.each(lockFiles)('detects %s as generated', (file) => {
896
+ const diffLines = [
897
+ `diff --git a/${file} b/${file}`,
898
+ `index abc123..def456 100644`,
899
+ `--- a/${file}`,
900
+ `+++ b/${file}`,
901
+ `@@ -1 +1 @@`,
902
+ `-old`,
903
+ `+new`,
904
+ ];
905
+ const summary = {
906
+ file,
907
+ insertions: 1,
908
+ deletions: 1,
909
+ binary: false,
910
+ };
911
+ const result = parser.parseFileBlock(diffLines.join('\n'), summary);
912
+ expect(result.isGenerated).toBe(true);
913
+ });
914
+ it('detects generated files by content (integration)', async () => {
915
+ const file = 'src/query.ts';
916
+ const diffLines = [
917
+ `diff --git a/${file} b/${file}`,
918
+ `index abc123..def456 100644`,
919
+ `--- a/${file}`,
920
+ `+++ b/${file}`,
921
+ `@@ -1 +1 @@`,
922
+ `-old`,
923
+ `+new`,
924
+ ];
925
+ const summary = {
926
+ file,
927
+ insertions: 1,
928
+ deletions: 1,
929
+ binary: false,
930
+ };
931
+ // Mock git.diff and git.diffSummary
932
+ const gitDiff = parser.git.diff;
933
+ const gitDiffSummary = parser.git.diffSummary;
934
+ // Check if revparse is already mocked by vi.mock('simple-git') structure, if not we add it.
935
+ // Actually (parser as any).git is the mock object returned by simpleGit().
936
+ // In line 8 of git-diff.test.ts: simpleGit: vi.fn(() => ({ revparse: vi.fn(), ... }))
937
+ // So revparse IS a mock.
938
+ gitDiff.mockResolvedValue(diffLines.join('\n'));
939
+ gitDiffSummary.mockResolvedValue({
940
+ files: [summary],
941
+ insertions: 1,
942
+ deletions: 1,
943
+ });
944
+ parser.git.revparse.mockResolvedValue('abc1234567890abcdef1234567890abcdef12');
945
+ // Mock getBlobContent to return generated content
946
+ // We need to spy on the prototype or the instance method?
947
+ // parser is instance.
948
+ // parser.getBlobContent is the method.
949
+ // But getBlobContent is on the class.
950
+ // modifying the instance method is easiest if it's not private (it is public-ish in TS but private in class def?)
951
+ // It is defined as `async getBlobContent(...)`.
952
+ // Since it's on the class, we can spy on it if we cast to any.
953
+ const getBlobContentSpy = vi.spyOn(parser, 'getBlobContent');
954
+ getBlobContentSpy.mockResolvedValue(Buffer.from('// @generated\nconst x = 1;'));
955
+ const response = await parser.parseDiff('HEAD', 'HEAD~1');
956
+ expect(response.files[0].path).toBe(file);
957
+ expect(response.files[0].isGenerated).toBe(true);
958
+ });
959
+ it('detects minified files as generated', () => {
960
+ const minFiles = ['script.min.js', 'style.min.css', 'vendor/lib.min.js'];
961
+ for (const file of minFiles) {
962
+ const diffLines = [
963
+ `diff --git a/${file} b/${file}`,
964
+ `index abc123..def456 100644`,
965
+ `--- a/${file}`,
966
+ `+++ b/${file}`,
967
+ `@@ -1 +1 @@`,
968
+ `-old`,
969
+ `+new`,
970
+ ];
971
+ const summary = {
972
+ file,
973
+ insertions: 1,
974
+ deletions: 1,
975
+ binary: false,
976
+ };
977
+ const result = parser.parseFileBlock(diffLines.join('\n'), summary);
978
+ expect(result.isGenerated).toBe(true);
979
+ }
980
+ });
981
+ it('detects source maps as generated', () => {
982
+ const file = 'main.js.map';
983
+ const diffLines = [
984
+ `diff --git a/${file} b/${file}`,
985
+ `index abc123..def456 100644`,
986
+ `--- a/${file}`,
987
+ `+++ b/${file}`,
988
+ `@@ -1 +1 @@`,
989
+ `-old`,
990
+ `+new`,
991
+ ];
992
+ const summary = {
993
+ file,
994
+ insertions: 1,
995
+ deletions: 1,
996
+ binary: false,
997
+ };
998
+ const result = parser.parseFileBlock(diffLines.join('\n'), summary);
999
+ expect(result.isGenerated).toBe(true);
1000
+ });
1001
+ it('does not mark normal files as generated', () => {
1002
+ const normalFiles = ['script.js', 'style.css', 'README.md', 'package.json'];
1003
+ for (const file of normalFiles) {
1004
+ const diffLines = [
1005
+ `diff --git a/${file} b/${file}`,
1006
+ `index abc123..def456 100644`,
1007
+ `--- a/${file}`,
1008
+ `+++ b/${file}`,
1009
+ `@@ -1 +1 @@`,
1010
+ `-old`,
1011
+ `+new`,
1012
+ ];
1013
+ const summary = {
1014
+ file,
1015
+ insertions: 1,
1016
+ deletions: 1,
1017
+ binary: false,
1018
+ };
1019
+ const result = parser.parseFileBlock(diffLines.join('\n'), summary);
1020
+ expect(result.isGenerated).toBe(false);
1021
+ }
1022
+ });
1023
+ it('detects binary lock files as generated', () => {
1024
+ const file = 'yarn.lock';
1025
+ const diffLines = [
1026
+ `diff --git a/${file} b/${file}`,
1027
+ `index abc123..def456 100644`,
1028
+ `--- a/${file}`,
1029
+ `+++ b/${file}`,
1030
+ `Binary files a/${file} and b/${file} differ`,
1031
+ ];
1032
+ const summary = {
1033
+ file,
1034
+ insertions: 0,
1035
+ deletions: 0,
1036
+ binary: true,
1037
+ };
1038
+ const result = parser.parseFileBlock(diffLines.join('\n'), summary);
1039
+ expect(result.isGenerated).toBe(true);
1040
+ });
1041
+ });
845
1042
  });
@@ -5,6 +5,7 @@ export interface DiffFile {
5
5
  additions: number;
6
6
  deletions: number;
7
7
  chunks: DiffChunk[];
8
+ isGenerated?: boolean;
8
9
  }
9
10
  export interface FileDiff {
10
11
  path: string;
@@ -31,6 +32,7 @@ export interface ParsedDiff {
31
32
  chunks: DiffChunk[];
32
33
  }
33
34
  export type DiffViewMode = 'side-by-side' | 'inline';
35
+ export type DiffSide = 'old' | 'new';
34
36
  export interface DiffResponse {
35
37
  commit: string;
36
38
  files: DiffFile[];
@@ -49,9 +51,10 @@ export interface Comment {
49
51
  body: string;
50
52
  timestamp: string;
51
53
  codeContent?: string;
54
+ side?: DiffSide;
52
55
  }
53
56
  export interface LineSelection {
54
- side: 'old' | 'new';
57
+ side: DiffSide;
55
58
  lineNumber: number;
56
59
  }
57
60
  export interface DiffComment {
@@ -62,7 +65,7 @@ export interface DiffComment {
62
65
  updatedAt: string;
63
66
  chunkHeader: string;
64
67
  position: {
65
- side: 'old' | 'new';
68
+ side: DiffSide;
66
69
  line: number | {
67
70
  start: number;
68
71
  end: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "difit",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view",
5
5
  "type": "module",
6
6
  "engines": {
@@ -46,7 +46,7 @@
46
46
  "diff": "^8.0.2",
47
47
  "express": "^5.1.0",
48
48
  "ink": "^6.0.1",
49
- "lucide-react": "^0.554.0",
49
+ "lucide-react": "^0.555.0",
50
50
  "open": "^11.0.0",
51
51
  "prism-react-renderer": "^2.4.1",
52
52
  "prismjs": "^1.30.0",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "devDependencies": {
59
59
  "@eslint/js": "^9.30.1",
60
- "@prettier/plugin-oxc": "^0.0.5",
60
+ "@prettier/plugin-oxc": "^0.1.0",
61
61
  "@tailwindcss/forms": "^0.5.10",
62
62
  "@tailwindcss/postcss": "^4.1.11",
63
63
  "@tailwindcss/typography": "^0.5.16",
@@ -113,5 +113,5 @@
113
113
  "publishConfig": {
114
114
  "access": "public"
115
115
  },
116
- "packageManager": "pnpm@10.22.0"
116
+ "packageManager": "pnpm@10.24.0"
117
117
  }