difit 2.0.2 → 2.0.4

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 (31) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/cli/index.test.js +17 -17
  3. package/dist/cli/utils.js +40 -2
  4. package/dist/cli/utils.test.js +33 -0
  5. package/dist/client/assets/{index-8bOJqzQC.js → index-BAqzWO4G.js} +51 -41
  6. package/dist/client/assets/index-Bo95t_L8.css +1 -0
  7. package/dist/client/assets/{prism-java-XP5rs1ZV.js → prism-java-qbeIxN8m.js} +1 -1
  8. package/dist/client/assets/{prism-php-CYxq20-J.js → prism-php-CkKTjm8O.js} +1 -1
  9. package/dist/client/assets/{prism-ruby-BoO_oIbk.js → prism-ruby-DoYrLzd_.js} +1 -1
  10. package/dist/client/assets/{prism-solidity-CHQMoOrF.js → prism-solidity-yzLt942n.js} +1 -1
  11. package/dist/client/index.html +2 -2
  12. package/dist/server/git-diff.js +0 -6
  13. package/dist/server/git-diff.test.js +26 -1
  14. package/dist/server/server.js +1 -1
  15. package/dist/server/server.test.js +3 -3
  16. package/dist/tui/App.js +4 -4
  17. package/dist/tui/components/DiffViewer.d.ts +1 -1
  18. package/dist/tui/components/DiffViewer.js +1 -1
  19. package/dist/tui/components/FileList.d.ts +1 -1
  20. package/dist/tui/components/FileList.js +1 -1
  21. package/dist/tui/components/SideBySideDiffViewer.d.ts +1 -1
  22. package/dist/tui/components/SideBySideDiffViewer.js +42 -39
  23. package/dist/tui/components/StatusBar.js +1 -1
  24. package/dist/tui/utils/parseDiff.d.ts +1 -1
  25. package/dist/tui/utils/parseDiff.js +1 -2
  26. package/dist/utils/gitUtils.d.ts +4 -0
  27. package/dist/utils/gitUtils.js +29 -0
  28. package/dist/utils/gitUtils.test.d.ts +1 -0
  29. package/dist/utils/gitUtils.test.js +63 -0
  30. package/package.json +3 -1
  31. package/dist/client/assets/index-sXbQL2XR.css +0 -1
package/dist/cli/index.js CHANGED
@@ -16,7 +16,7 @@ program
16
16
  .argument('[commit-ish]', 'Git commit, tag, branch, HEAD~n reference, or "working"/"staged"/"."', 'HEAD')
17
17
  .argument('[compare-with]', 'Optional: Compare with this commit/branch (shows diff between commit-ish and compare-with)')
18
18
  .option('--port <port>', 'preferred port (auto-assigned if occupied)', parseInt)
19
- .option('--host <host>', 'host address to bind', '127.0.0.1')
19
+ .option('--host <host>', 'host address to bind', '')
20
20
  .option('--no-open', 'do not automatically open browser')
21
21
  .option('--mode <mode>', 'diff mode (side-by-side or inline)', 'side-by-side')
22
22
  .option('--tui', 'use terminal UI instead of web interface')
@@ -107,7 +107,7 @@ describe('CLI index.ts', () => {
107
107
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
108
108
  .argument('[compare-with]', 'compare-with')
109
109
  .option('--port <port>', 'port', parseInt)
110
- .option('--host <host>', 'host', '127.0.0.1')
110
+ .option('--host <host>', 'host', '')
111
111
  .option('--no-open', 'no-open')
112
112
  .option('--mode <mode>', 'mode', 'side-by-side')
113
113
  .option('--tui', 'tui')
@@ -149,7 +149,7 @@ describe('CLI index.ts', () => {
149
149
  targetCommitish: expectedTarget,
150
150
  baseCommitish: expectedBase,
151
151
  preferredPort: undefined,
152
- host: '127.0.0.1',
152
+ host: '',
153
153
  openBrowser: true,
154
154
  mode: 'side-by-side',
155
155
  });
@@ -184,7 +184,7 @@ describe('CLI index.ts', () => {
184
184
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
185
185
  .argument('[compare-with]', 'compare-with')
186
186
  .option('--port <port>', 'port', parseInt)
187
- .option('--host <host>', 'host', '127.0.0.1')
187
+ .option('--host <host>', 'host', '')
188
188
  .option('--no-open', 'no-open')
189
189
  .option('--mode <mode>', 'mode', 'side-by-side')
190
190
  .option('--tui', 'tui')
@@ -206,7 +206,7 @@ describe('CLI index.ts', () => {
206
206
  targetCommitish: 'HEAD',
207
207
  baseCommitish: 'HEAD^',
208
208
  preferredPort: expectedOptions.port,
209
- host: expectedOptions.host || '127.0.0.1',
209
+ host: expectedOptions.host || '',
210
210
  openBrowser: expectedOptions.open !== false,
211
211
  mode: expectedOptions.mode || 'side-by-side',
212
212
  };
@@ -224,7 +224,7 @@ describe('CLI index.ts', () => {
224
224
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
225
225
  .argument('[compare-with]', 'compare-with')
226
226
  .option('--port <port>', 'port', parseInt)
227
- .option('--host <host>', 'host', '127.0.0.1')
227
+ .option('--host <host>', 'host', '')
228
228
  .option('--no-open', 'no-open')
229
229
  .option('--mode <mode>', 'mode', 'side-by-side')
230
230
  .option('--tui', 'tui')
@@ -255,7 +255,7 @@ describe('CLI index.ts', () => {
255
255
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
256
256
  .argument('[compare-with]', 'compare-with')
257
257
  .option('--port <port>', 'port', parseInt)
258
- .option('--host <host>', 'host', '127.0.0.1')
258
+ .option('--host <host>', 'host', '')
259
259
  .option('--no-open', 'no-open')
260
260
  .option('--mode <mode>', 'mode', 'side-by-side')
261
261
  .option('--tui', 'tui')
@@ -291,7 +291,7 @@ describe('CLI index.ts', () => {
291
291
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
292
292
  .argument('[compare-with]', 'compare-with')
293
293
  .option('--port <port>', 'port', parseInt)
294
- .option('--host <host>', 'host', '127.0.0.1')
294
+ .option('--host <host>', 'host', '')
295
295
  .option('--no-open', 'no-open')
296
296
  .option('--mode <mode>', 'mode', 'side-by-side')
297
297
  .option('--tui', 'tui')
@@ -326,7 +326,7 @@ describe('CLI index.ts', () => {
326
326
  targetCommitish: 'abc123',
327
327
  baseCommitish: 'def456',
328
328
  preferredPort: undefined,
329
- host: '127.0.0.1',
329
+ host: '',
330
330
  openBrowser: true,
331
331
  mode: 'side-by-side',
332
332
  });
@@ -337,7 +337,7 @@ describe('CLI index.ts', () => {
337
337
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
338
338
  .argument('[compare-with]', 'compare-with')
339
339
  .option('--port <port>', 'port', parseInt)
340
- .option('--host <host>', 'host', '127.0.0.1')
340
+ .option('--host <host>', 'host', '')
341
341
  .option('--no-open', 'no-open')
342
342
  .option('--mode <mode>', 'mode', 'side-by-side')
343
343
  .option('--tui', 'tui')
@@ -370,7 +370,7 @@ describe('CLI index.ts', () => {
370
370
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
371
371
  .argument('[compare-with]', 'compare-with')
372
372
  .option('--port <port>', 'port', parseInt)
373
- .option('--host <host>', 'host', '127.0.0.1')
373
+ .option('--host <host>', 'host', '')
374
374
  .option('--no-open', 'no-open')
375
375
  .option('--mode <mode>', 'mode', 'side-by-side')
376
376
  .option('--tui', 'tui')
@@ -413,7 +413,7 @@ describe('CLI index.ts', () => {
413
413
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
414
414
  .argument('[compare-with]', 'compare-with')
415
415
  .option('--port <port>', 'port', parseInt)
416
- .option('--host <host>', 'host', '127.0.0.1')
416
+ .option('--host <host>', 'host', '')
417
417
  .option('--no-open', 'no-open')
418
418
  .option('--mode <mode>', 'mode', 'side-by-side')
419
419
  .option('--tui', 'tui')
@@ -447,7 +447,7 @@ describe('CLI index.ts', () => {
447
447
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
448
448
  .argument('[compare-with]', 'compare-with')
449
449
  .option('--port <port>', 'port', parseInt)
450
- .option('--host <host>', 'host', '127.0.0.1')
450
+ .option('--host <host>', 'host', '')
451
451
  .option('--no-open', 'no-open')
452
452
  .option('--mode <mode>', 'mode', 'side-by-side')
453
453
  .option('--tui', 'tui')
@@ -474,7 +474,7 @@ describe('CLI index.ts', () => {
474
474
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
475
475
  .argument('[compare-with]', 'compare-with')
476
476
  .option('--port <port>', 'port', parseInt)
477
- .option('--host <host>', 'host', '127.0.0.1')
477
+ .option('--host <host>', 'host', '')
478
478
  .option('--no-open', 'no-open')
479
479
  .option('--mode <mode>', 'mode', 'side-by-side')
480
480
  .option('--tui', 'tui')
@@ -528,7 +528,7 @@ describe('CLI index.ts', () => {
528
528
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
529
529
  .argument('[compare-with]', 'compare-with')
530
530
  .option('--port <port>', 'port', parseInt)
531
- .option('--host <host>', 'host', '127.0.0.1')
531
+ .option('--host <host>', 'host', '')
532
532
  .option('--no-open', 'no-open')
533
533
  .option('--mode <mode>', 'mode', 'side-by-side')
534
534
  .option('--tui', 'tui')
@@ -566,7 +566,7 @@ describe('CLI index.ts', () => {
566
566
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
567
567
  .argument('[compare-with]', 'compare-with')
568
568
  .option('--port <port>', 'port', parseInt)
569
- .option('--host <host>', 'host', '127.0.0.1')
569
+ .option('--host <host>', 'host', '')
570
570
  .option('--no-open', 'no-open')
571
571
  .option('--mode <mode>', 'mode', 'side-by-side')
572
572
  .option('--tui', 'tui')
@@ -604,7 +604,7 @@ describe('CLI index.ts', () => {
604
604
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
605
605
  .argument('[compare-with]', 'compare-with')
606
606
  .option('--port <port>', 'port', parseInt)
607
- .option('--host <host>', 'host', '127.0.0.1')
607
+ .option('--host <host>', 'host', '')
608
608
  .option('--no-open', 'no-open')
609
609
  .option('--mode <mode>', 'mode', 'side-by-side')
610
610
  .option('--tui', 'tui')
@@ -653,7 +653,7 @@ describe('CLI index.ts', () => {
653
653
  .argument('[commit-ish]', 'commit-ish', 'HEAD')
654
654
  .argument('[compare-with]', 'compare-with')
655
655
  .option('--port <port>', 'port', parseInt)
656
- .option('--host <host>', 'host', '127.0.0.1')
656
+ .option('--host <host>', 'host', '')
657
657
  .option('--no-open', 'no-open')
658
658
  .option('--mode <mode>', 'mode', 'side-by-side')
659
659
  .option('--tui', 'tui')
package/dist/cli/utils.js CHANGED
@@ -21,9 +21,47 @@ export function validateCommitish(commitish) {
21
21
  /^[a-f0-9]{4,40}\^+$/i, // SHA hashes with ^ suffix (parent references)
22
22
  /^[a-f0-9]{4,40}~\d+$/i, // SHA hashes with ~N suffix (ancestor references)
23
23
  /^HEAD(~\d+|\^\d*)*$/, // HEAD, HEAD~1, HEAD^, HEAD^2, etc.
24
- /^[a-zA-Z][a-zA-Z0-9_\-/.@]*$/, // branch names, tags (must start with letter, no ^ or ~ in middle)
25
24
  ];
26
- return validPatterns.some((pattern) => pattern.test(trimmed));
25
+ // Check if it matches any specific patterns first
26
+ if (validPatterns.some((pattern) => pattern.test(trimmed))) {
27
+ return true;
28
+ }
29
+ // For branch names, use git's rules
30
+ return isValidBranchName(trimmed);
31
+ }
32
+ function isValidBranchName(name) {
33
+ // Git branch name rules
34
+ if (name.startsWith('-'))
35
+ return false; // Cannot start with dash
36
+ if (name.endsWith('.'))
37
+ return false; // Cannot end with dot
38
+ if (name === '@')
39
+ return false; // Cannot be just @
40
+ if (name.includes('..'))
41
+ return false; // No consecutive dots
42
+ if (name.includes('@{'))
43
+ return false; // No @{ sequence
44
+ if (name.includes('//'))
45
+ return false; // No consecutive slashes
46
+ if (name.startsWith('/') || name.endsWith('/'))
47
+ return false; // No leading/trailing slashes
48
+ if (name.endsWith('.lock'))
49
+ return false; // Cannot end with .lock
50
+ // Check for forbidden characters
51
+ const forbiddenChars = /[~^:?*[\\\x00-\x20\x7F]/;
52
+ if (forbiddenChars.test(name))
53
+ return false;
54
+ // Check path components
55
+ const components = name.split('/');
56
+ for (const component of components) {
57
+ if (component === '')
58
+ return false; // Empty component
59
+ if (component.startsWith('.'))
60
+ return false; // Component cannot start with dot
61
+ if (component.endsWith('.lock'))
62
+ return false; // Component cannot end with .lock
63
+ }
64
+ return true;
27
65
  }
28
66
  export function shortHash(hash) {
29
67
  return hash.substring(0, 7);
@@ -28,9 +28,22 @@ describe('CLI Utils', () => {
28
28
  expect(validateCommitish('HEAD~2^1')).toBe(true);
29
29
  });
30
30
  it('should validate branch names', () => {
31
+ // Valid branch names according to git rules
31
32
  expect(validateCommitish('main')).toBe(true);
32
33
  expect(validateCommitish('feature/new-feature')).toBe(true);
33
34
  expect(validateCommitish('develop')).toBe(true);
35
+ expect(validateCommitish('feature-123')).toBe(true); // dash and numbers (not at start)
36
+ expect(validateCommitish('feature_branch')).toBe(true); // underscore
37
+ expect(validateCommitish('hotfix@bug')).toBe(true); // @ character (not followed by {)
38
+ expect(validateCommitish('feature+new')).toBe(true); // plus character
39
+ expect(validateCommitish('feature=test')).toBe(true); // equals character
40
+ expect(validateCommitish('feature!important')).toBe(true); // exclamation
41
+ expect(validateCommitish('feature,list')).toBe(true); // comma
42
+ expect(validateCommitish('feature;test')).toBe(true); // semicolon
43
+ expect(validateCommitish('feature"quoted"')).toBe(true); // quotes
44
+ expect(validateCommitish("feature'quoted'")).toBe(true); // single quotes
45
+ expect(validateCommitish('release/v2.3.1')).toBe(true); // version numbers
46
+ expect(validateCommitish('bugfix/login-timeout')).toBe(true); // path with dash
34
47
  });
35
48
  it('should validate special cases', () => {
36
49
  expect(validateCommitish('.')).toBe(true); // working directory diff
@@ -40,6 +53,26 @@ describe('CLI Utils', () => {
40
53
  expect(validateCommitish(' ')).toBe(false);
41
54
  expect(validateCommitish('HEAD~')).toBe(false);
42
55
  expect(validateCommitish('abc')).toBe(true); // short hashes are valid
56
+ // Invalid branch names according to git rules
57
+ expect(validateCommitish('-feature')).toBe(false); // cannot start with dash
58
+ expect(validateCommitish('feature.')).toBe(false); // cannot end with dot
59
+ expect(validateCommitish('@')).toBe(false); // cannot be just @
60
+ expect(validateCommitish('feature..test')).toBe(false); // no consecutive dots
61
+ expect(validateCommitish('feature@{upstream}')).toBe(false); // no @{ sequence
62
+ expect(validateCommitish('feature//test')).toBe(false); // no consecutive slashes
63
+ expect(validateCommitish('/feature')).toBe(false); // cannot start with slash
64
+ expect(validateCommitish('feature/')).toBe(false); // cannot end with slash
65
+ expect(validateCommitish('feature.lock')).toBe(false); // cannot end with .lock
66
+ expect(validateCommitish('feature^invalid')).toBe(false); // ^ not allowed
67
+ expect(validateCommitish('feature~invalid')).toBe(false); // ~ not allowed
68
+ expect(validateCommitish('feature:invalid')).toBe(false); // : not allowed
69
+ expect(validateCommitish('feature?invalid')).toBe(false); // ? not allowed
70
+ expect(validateCommitish('feature*invalid')).toBe(false); // * not allowed
71
+ expect(validateCommitish('feature[invalid')).toBe(false); // [ not allowed
72
+ expect(validateCommitish('feature\\invalid')).toBe(false); // \ not allowed
73
+ expect(validateCommitish('feature invalid')).toBe(false); // space not allowed
74
+ expect(validateCommitish('feature/.hidden')).toBe(false); // component cannot start with dot
75
+ expect(validateCommitish('feature/test.lock')).toBe(false); // component cannot end with .lock
43
76
  });
44
77
  it('should reject non-string input', () => {
45
78
  expect(validateCommitish(null)).toBe(false);