difit 2.0.11 → 2.1.1

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 (32) hide show
  1. package/README.ja.md +26 -9
  2. package/README.ko.md +20 -3
  3. package/README.md +20 -3
  4. package/README.zh.md +26 -9
  5. package/dist/cli/index.js +50 -0
  6. package/dist/cli/index.test.js +141 -12
  7. package/dist/cli/utils.js +2 -2
  8. package/dist/cli/utils.test.js +10 -1
  9. package/dist/client/assets/index-CueWm3qS.css +1 -0
  10. package/dist/client/assets/index-U2EYNr4O.js +215 -0
  11. package/dist/client/assets/{prism-csharp-Dc46Fjt0.js → prism-csharp-B2ldtMsY.js} +1 -1
  12. package/dist/client/assets/{prism-java-CqBdPW_L.js → prism-java-n1ck9mYw.js} +1 -1
  13. package/dist/client/assets/{prism-php-BLhwjsTl.js → prism-php-DiCmaEbX.js} +1 -1
  14. package/dist/client/assets/prism-protobuf-DiQ_z8B5.js +1 -0
  15. package/dist/client/assets/{prism-ruby-ExhPumJe.js → prism-ruby-qa8qRdnq.js} +1 -1
  16. package/dist/client/assets/{prism-solidity-BCgmGzF-.js → prism-solidity-DDmtWXtT.js} +1 -1
  17. package/dist/client/index.html +2 -2
  18. package/dist/server/file-watcher.d.ts +23 -0
  19. package/dist/server/file-watcher.js +236 -0
  20. package/dist/server/file-watcher.test.d.ts +1 -0
  21. package/dist/server/file-watcher.test.js +225 -0
  22. package/dist/server/git-diff.d.ts +2 -0
  23. package/dist/server/git-diff.js +47 -4
  24. package/dist/server/git-diff.test.js +209 -0
  25. package/dist/server/server.d.ts +5 -2
  26. package/dist/server/server.js +66 -16
  27. package/dist/server/server.test.js +3 -3
  28. package/dist/types/watch.d.ts +30 -0
  29. package/dist/types/watch.js +8 -0
  30. package/package.json +3 -1
  31. package/dist/client/assets/index-B6vRltPu.css +0 -1
  32. package/dist/client/assets/index-CjocZrF8.js +0 -200
package/README.ja.md CHANGED
@@ -6,6 +6,8 @@
6
6
  <a href="./README.md">English</a> | 日本語 | <a href="./README.zh.md">简体中文</a> | <a href="./README.ko.md">한국어</a>
7
7
  </p>
8
8
 
9
+ ![difit screenshot](docs/images/screenshot.png)
10
+
9
11
  **difit**は、ローカルのgit上にある差分をGitHub風のビューアで閲覧・レビューできるCLIツールです。見やすい表示に加え、コメントはAIへのプロンプトとしてコピーできます。AI時代のローカルコードレビューツール!
10
12
 
11
13
  ## ✨ 機能
@@ -41,7 +43,7 @@ npx difit feature # featureブランチの最新コミット
41
43
  ### 2つのコミットを比較
42
44
 
43
45
  ```bash
44
- npx difit HEAD main # HEADとmainブランチを比較
46
+ npx difit @ main # mainブランチと比較(@はHEADのエイリアス)
45
47
  npx difit feature main # ブランチ間を比較
46
48
  npx difit . origin/main # 作業ディレクトリとリモートmainを比較
47
49
  ```
@@ -76,6 +78,21 @@ Enterprise ServerのPRを表示する場合、あなたのEnterprise Serverイ
76
78
  2. 適切なスコープでパーソナルアクセストークンを生成
77
79
  3. `GITHUB_TOKEN`環境変数として設定
78
80
 
81
+ ### 標準入力
82
+
83
+ パイプを使用して標準入力経由で統一diff形式を渡すことで、任意のツールからのdiffをdifitで表示できます。
84
+
85
+ ```bash
86
+ # 他のツールからのdiffを表示
87
+ diff -u file1.txt file2.txt | npx difit
88
+
89
+ # 保存されたパッチをレビュー
90
+ cat changes.patch | npx difit
91
+
92
+ # マージベースとの比較
93
+ git diff --merge-base main feature | npx difit
94
+ ```
95
+
79
96
  ## ⚙️ CLIオプション
80
97
 
81
98
  | フラグ | デフォルト | 説明 |
@@ -83,7 +100,7 @@ Enterprise ServerのPRを表示する場合、あなたのEnterprise Serverイ
83
100
  | `<target>` | HEAD | コミットハッシュ、タグ、HEAD~n、ブランチ、または特別な引数 |
84
101
  | `[compare-with]` | - | 比較対象の2番目のコミット(2つの間のdiffを表示) |
85
102
  | `--pr <url>` | - | レビューするGitHub PRのURL(例:https://github.com/owner/repo/pull/123) |
86
- | `--port` | 3000 | 優先ポート。使用中の場合は+1にフォールバック |
103
+ | `--port` | 4966 | 優先ポート。使用中の場合は+1にフォールバック |
87
104
  | `--host` | 127.0.0.1 | サーバーをバインドするホストアドレス(外部からアクセスしたい場合は0.0.0.0を指定) |
88
105
  | `--no-open` | false | ブラウザを自動的に開かない |
89
106
  | `--mode` | side-by-side | 表示モード。inline`または`side-by-side` |
@@ -116,13 +133,13 @@ src/components/Button.tsx:L42-L48 # この行が自動的に追加されます
116
133
 
117
134
  ## 🎨 シンタックスハイライト対応言語
118
135
 
119
- - **JavaScript/TypeScript**:`.js`、`.jsx`、`.ts`、`.tsx`
120
- - **Web技術**:HTMLCSSJSONXMLMarkdown
121
- - **シェルスクリプト**:`.sh`、`.bash`、`.zsh`、`.fish`
122
- - **バックエンド言語**:PHPSQLRubyJavaScala
123
- - **システム言語**:CC++、C#、RustGo
124
- - **モバイル言語**:SwiftKotlinDart
125
- - **その他**:PythonYAMLSolidityVimスクリプト
136
+ - **JavaScript/TypeScript**:`.js`, `.jsx`, `.ts`, `.tsx`
137
+ - **Web技術**:HTML, CSS, JSON, XML, Markdown
138
+ - **シェルスクリプト**:`.sh`, `.bash`, `.zsh`, `.fish`
139
+ - **バックエンド言語**:PHP, SQL, Ruby, Java, Scala
140
+ - **システム言語**:C, C++, C#, Rust, Go
141
+ - **モバイル言語**:Swift, Kotlin, Dart
142
+ - **その他**:Python, Protobuf, YAML, Solidity, Vim Script
126
143
 
127
144
  ## 🛠️ 開発
128
145
 
package/README.ko.md CHANGED
@@ -6,6 +6,8 @@
6
6
  <a href="./README.md">English</a> | <a href="./README.ja.md">日本語</a> | <a href="./README.zh.md">简体中文</a> | 한국어
7
7
  </p>
8
8
 
9
+ ![difit screenshot](docs/images/screenshot.png)
10
+
9
11
  **difit**은 GitHub 스타일 뷰어로 로컬 git diff를 보고 검토할 수 있는 CLI 도구입니다. 깔끔한 시각적 효과와 함께 코멘트를 AI용 프롬프트로 복사할 수 있습니다. AI 시대의 로컬 코드 리뷰 도구!
10
12
 
11
13
  ## ✨ 기능
@@ -41,7 +43,7 @@ npx difit feature # feature 브랜치의 최신 커밋
41
43
  ### 두 커밋 비교
42
44
 
43
45
  ```bash
44
- npx difit HEAD main # HEAD와 main 브랜치 비교
46
+ npx difit @ main # main 브랜치와 비교 (@는 HEAD의 별칭)
45
47
  npx difit feature main # 브랜치 간 비교
46
48
  npx difit . origin/main # 작업 디렉토리와 원격 main 비교
47
49
  ```
@@ -76,6 +78,21 @@ Enterprise Server PR의 경우 귀하의 Enterprise Server 인스턴스에서
76
78
  2. 적절한 범위로 개인 액세스 토큰 생성
77
79
  3. `GITHUB_TOKEN` 환경 변수로 설정
78
80
 
81
+ ### 표준 입력
82
+
83
+ 파이프를 사용하여 표준 입력을 통해 통합 diff를 전달하면 모든 도구의 diff를 difit으로 볼 수 있습니다.
84
+
85
+ ```bash
86
+ # 다른 도구의 diff 보기
87
+ diff -u file1.txt file2.txt | npx difit
88
+
89
+ # 저장된 패치 검토
90
+ cat changes.patch | npx difit
91
+
92
+ # 머지 베이스와 비교
93
+ git diff --merge-base main feature | npx difit
94
+ ```
95
+
79
96
  ## ⚙️ CLI 옵션
80
97
 
81
98
  | 플래그 | 기본값 | 설명 |
@@ -83,7 +100,7 @@ Enterprise Server PR의 경우 귀하의 Enterprise Server 인스턴스에서
83
100
  | `<target>` | HEAD | 커밋 해시, 태그, HEAD~n, 브랜치 또는 특수 인수 |
84
101
  | `[compare-with]` | - | 비교할 선택적 두 번째 커밋 (둘 사이의 diff 표시) |
85
102
  | `--pr <url>` | - | 검토할 GitHub PR URL (예: https://github.com/owner/repo/pull/123) |
86
- | `--port` | 3000 | 선호 포트; 사용 중인 경우 +1로 대체 |
103
+ | `--port` | 4966 | 선호 포트; 사용 중인 경우 +1로 대체 |
87
104
  | `--host` | 127.0.0.1 | 서버를 바인딩할 호스트 주소 (외부 액세스는 0.0.0.0 사용) |
88
105
  | `--no-open` | false | 브라우저를 자동으로 열지 않음 |
89
106
  | `--mode` | side-by-side | 표시 모드: `inline` 또는 `side-by-side` |
@@ -122,7 +139,7 @@ src/components/Button.tsx:L42-L48 # 이 줄은 자동으로 추가됩니다
122
139
  - **백엔드 언어**: PHP, SQL, Ruby, Java, Scala
123
140
  - **시스템 언어**: C, C++, C#, Rust, Go
124
141
  - **모바일 언어**: Swift, Kotlin, Dart
125
- - **기타**: Python, YAML, Solidity, Vim 스크립트
142
+ - **기타**: Python, Protobuf, YAML, Solidity, Vim 스크립트
126
143
 
127
144
  ## 🛠️ 개발
128
145
 
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  English | <a href="./README.ja.md">日本語</a> | <a href="./README.zh.md">简体中文</a> | <a href="./README.ko.md">한국어</a>
7
7
  </p>
8
8
 
9
+ ![difit screenshot](docs/images/screenshot.png)
10
+
9
11
  **difit** is a CLI tool that lets you view and review local git diffs with a GitHub-style viewer. In addition to clean visuals, comments can be copied as prompts for AI. The local code review tool for the AI era!
10
12
 
11
13
  ## ✨ Features
@@ -41,7 +43,7 @@ npx difit feature # Latest commit on feature branch
41
43
  ### Compare two commits
42
44
 
43
45
  ```bash
44
- npx difit HEAD main # Compare HEAD with main branch
46
+ npx difit @ main # Compare with main branch (@ is alias for HEAD)
45
47
  npx difit feature main # Compare branches
46
48
  npx difit . origin/main # Compare working directory with remote main
47
49
  ```
@@ -76,6 +78,21 @@ For Enterprise Server PRs, you must set a token generated on YOUR Enterprise Ser
76
78
  2. Generate a personal access token with appropriate scopes
77
79
  3. Set it as `GITHUB_TOKEN` environment variable
78
80
 
81
+ ### Stdin
82
+
83
+ By using a pipe to pass unified diffs via stdin, you can view diffs from any tool with difit.
84
+
85
+ ```bash
86
+ # View diffs from other tools
87
+ diff -u file1.txt file2.txt | npx difit
88
+
89
+ # Review saved patches
90
+ cat changes.patch | npx difit
91
+
92
+ # Compare against merge base
93
+ git diff --merge-base main feature | npx difit
94
+ ```
95
+
79
96
  ## ⚙️ CLI Options
80
97
 
81
98
  | Flag | Default | Description |
@@ -83,7 +100,7 @@ For Enterprise Server PRs, you must set a token generated on YOUR Enterprise Ser
83
100
  | `<target>` | HEAD | Commit hash, tag, HEAD~n, branch, or special arguments |
84
101
  | `[compare-with]` | - | Optional second commit to compare with (shows diff between the two) |
85
102
  | `--pr <url>` | - | GitHub PR URL to review (e.g., https://github.com/owner/repo/pull/123) |
86
- | `--port` | 3000 | Preferred port; falls back to +1 if occupied |
103
+ | `--port` | 4966 | Preferred port; falls back to +1 if occupied |
87
104
  | `--host` | 127.0.0.1 | Host address to bind server to (use 0.0.0.0 for external access) |
88
105
  | `--no-open` | false | Don't automatically open browser |
89
106
  | `--mode` | side-by-side | Display mode: `inline` or `side-by-side` |
@@ -122,7 +139,7 @@ This section is unnecessary
122
139
  - **Backend Languages**: PHP, SQL, Ruby, Java, Scala
123
140
  - **Systems Languages**: C, C++, C#, Rust, Go
124
141
  - **Mobile Languages**: Swift, Kotlin, Dart
125
- - **Others**: Python, YAML, Solidity, Vim script
142
+ - **Others**: Python, Protobuf, YAML, Solidity, Vim script
126
143
 
127
144
  ## 🛠️ Development
128
145
 
package/README.zh.md CHANGED
@@ -6,6 +6,8 @@
6
6
  <a href="./README.md">English</a> | <a href="./README.ja.md">日本語</a> | 简体中文 | <a href="./README.ko.md">한국어</a>
7
7
  </p>
8
8
 
9
+ ![difit screenshot](docs/images/screenshot.png)
10
+
9
11
  **difit** 是一个让你使用 GitHub 风格查看器查看和审查本地 git 差异的 CLI 工具。除了清晰的视觉效果外,评论还可以作为 AI 提示进行复制。AI 时代的本地代码审查工具!
10
12
 
11
13
  ## ✨ 功能
@@ -41,7 +43,7 @@ npx difit feature # feature 分支上的最新提交
41
43
  ### 比较两个提交
42
44
 
43
45
  ```bash
44
- npx difit HEAD main # 比较 HEAD 与 main 分支
46
+ npx difit @ main # 与 main 分支比较(@ 是 HEAD 的别名)
45
47
  npx difit feature main # 比较分支
46
48
  npx difit . origin/main # 比较工作目录与远程 main
47
49
  ```
@@ -76,6 +78,21 @@ difit 使用以下方式自动处理 GitHub 认证:
76
78
  2. 生成具有适当范围的个人访问令牌
77
79
  3. 将其设置为 `GITHUB_TOKEN` 环境变量
78
80
 
81
+ ### 标准输入
82
+
83
+ 通过使用管道通过标准输入传递统一差异,您可以使用 difit 查看来自任何工具的差异。
84
+
85
+ ```bash
86
+ # 查看来自其他工具的差异
87
+ diff -u file1.txt file2.txt | npx difit
88
+
89
+ # 审查保存的补丁
90
+ cat changes.patch | npx difit
91
+
92
+ # 与合并基础比较
93
+ git diff --merge-base main feature | npx difit
94
+ ```
95
+
79
96
  ## ⚙️ CLI 选项
80
97
 
81
98
  | 标志 | 默认值 | 描述 |
@@ -83,7 +100,7 @@ difit 使用以下方式自动处理 GitHub 认证:
83
100
  | `<target>` | HEAD | 提交哈希、标签、HEAD~n、分支或特殊参数 |
84
101
  | `[compare-with]` | - | 要比较的可选第二个提交(显示两者之间的差异) |
85
102
  | `--pr <url>` | - | 要审查的 GitHub PR URL(例如:https://github.com/owner/repo/pull/123) |
86
- | `--port` | 3000 | 首选端口;如果被占用则回退到 +1 |
103
+ | `--port` | 4966 | 首选端口;如果被占用则回退到 +1 |
87
104
  | `--host` | 127.0.0.1 | 绑定服务器的主机地址(使用 0.0.0.0 进行外部访问) |
88
105
  | `--no-open` | false | 不自动打开浏览器 |
89
106
  | `--mode` | side-by-side | 显示模式:`inline` 或 `side-by-side` |
@@ -116,13 +133,13 @@ src/components/Button.tsx:L42-L48 # 此行自动添加
116
133
 
117
134
  ## 🎨 语法高亮语言
118
135
 
119
- - **JavaScript/TypeScript**:`.js`、`.jsx`、`.ts`、`.tsx`
120
- - **Web 技术**:HTMLCSSJSONXMLMarkdown
121
- - **Shell 脚本**:`.sh`、`.bash`、`.zsh`、`.fish`
122
- - **后端语言**:PHPSQLRubyJavaScala
123
- - **系统语言**:CC++、C#、RustGo
124
- - **移动语言**:SwiftKotlinDart
125
- - **其他**:PythonYAMLSolidityVim 脚本
136
+ - **JavaScript/TypeScript**:`.js`, `.jsx`, `.ts`, `.tsx`
137
+ - **Web 技术**:HTML, CSS, JSON, XML, Markdown
138
+ - **Shell 脚本**:`.sh`, `.bash`, `.zsh`, `.fish`
139
+ - **后端语言**:PHP, SQL, Ruby, Java, Scala
140
+ - **系统语言**:C, C++, C#, Rust, Go
141
+ - **移动语言**:Swift, Kotlin, Dart
142
+ - **其他**:Python, Protobuf, YAML, Solidity, Vim Script
126
143
 
127
144
  ## 🛠️ 开发
128
145
 
package/dist/cli/index.js CHANGED
@@ -4,10 +4,28 @@ import React from 'react';
4
4
  import { simpleGit } from 'simple-git';
5
5
  import pkg from '../../package.json' with { type: 'json' };
6
6
  import { startServer } from '../server/server.js';
7
+ import { DiffMode } from '../types/watch.js';
7
8
  import { findUntrackedFiles, markFilesIntentToAdd, promptUser, validateDiffArguments, resolvePrCommits, } from './utils.js';
8
9
  function isSpecialArg(arg) {
9
10
  return arg === 'working' || arg === 'staged' || arg === '.';
10
11
  }
12
+ function determineDiffMode(targetCommitish, compareWith) {
13
+ // If comparing specific commits/branches (not involving HEAD), no watching needed
14
+ if (compareWith && targetCommitish !== 'HEAD') {
15
+ return DiffMode.SPECIFIC;
16
+ }
17
+ if (targetCommitish === 'working') {
18
+ return DiffMode.WORKING;
19
+ }
20
+ if (targetCommitish === 'staged') {
21
+ return DiffMode.STAGED;
22
+ }
23
+ if (targetCommitish === '.') {
24
+ return DiffMode.DOT;
25
+ }
26
+ // Default mode: HEAD^ vs HEAD or HEAD vs other commits (watch for HEAD changes)
27
+ return DiffMode.DEFAULT;
28
+ }
11
29
  const program = new Command();
12
30
  program
13
31
  .name('difit')
@@ -24,6 +42,29 @@ program
24
42
  .option('--clean', 'start with a clean slate by clearing all existing comments')
25
43
  .action(async (commitish, compareWith, options) => {
26
44
  try {
45
+ // Check if we should read from stdin
46
+ const shouldReadStdin = !process.stdin.isTTY || commitish === '-';
47
+ if (shouldReadStdin) {
48
+ // Read unified diff from stdin
49
+ const diffContent = await readStdin();
50
+ if (!diffContent.trim()) {
51
+ console.error('Error: No diff content received from stdin');
52
+ process.exit(1);
53
+ }
54
+ // Start server with stdin diff
55
+ const { url } = await startServer({
56
+ stdinDiff: diffContent,
57
+ preferredPort: options.port,
58
+ host: options.host,
59
+ openBrowser: options.open,
60
+ mode: options.mode,
61
+ clearComments: options.clean,
62
+ });
63
+ console.log(`\n🚀 difit server started on ${url}`);
64
+ console.log(`📋 Reviewing: diff from stdin`);
65
+ console.log('\nPress Ctrl+C to stop the server');
66
+ return;
67
+ }
27
68
  // Determine target and base commitish
28
69
  let targetCommitish = commitish;
29
70
  let baseCommitish;
@@ -88,6 +129,7 @@ program
88
129
  process.exit(1);
89
130
  }
90
131
  }
132
+ const diffMode = determineDiffMode(targetCommitish, compareWith);
91
133
  const { url, port, isEmpty } = await startServer({
92
134
  targetCommitish,
93
135
  baseCommitish,
@@ -96,6 +138,7 @@ program
96
138
  openBrowser: options.open,
97
139
  mode: options.mode,
98
140
  clearComments: options.clean,
141
+ diffMode,
99
142
  });
100
143
  console.log(`\n🚀 difit server started on ${url}`);
101
144
  console.log(`📋 Reviewing: ${targetCommitish}`);
@@ -137,6 +180,13 @@ program
137
180
  });
138
181
  program.parse();
139
182
  // Check for untracked files and prompt user to add them for diff visibility
183
+ async function readStdin() {
184
+ const chunks = [];
185
+ for await (const chunk of process.stdin) {
186
+ chunks.push(chunk);
187
+ }
188
+ return Buffer.concat(chunks).toString('utf8');
189
+ }
140
190
  async function handleUntrackedFiles(git) {
141
191
  const files = await findUntrackedFiles(git);
142
192
  if (files.length === 0) {
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import React from 'react';
3
3
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
4
+ import { DiffMode } from '../types/watch.js';
4
5
  // Mock all external dependencies
5
6
  vi.mock('simple-git');
6
7
  vi.mock('../server/server.js');
@@ -37,8 +38,8 @@ describe('CLI index.ts', () => {
37
38
  vi.mocked(simpleGit).mockReturnValue(mockGit);
38
39
  mockStartServer = vi.mocked(startServer);
39
40
  mockStartServer.mockResolvedValue({
40
- port: 3000,
41
- url: 'http://localhost:3000',
41
+ port: 4966,
42
+ url: 'http://localhost:4966',
42
43
  isEmpty: false,
43
44
  });
44
45
  mockPromptUser = vi.mocked(promptUser);
@@ -421,8 +422,8 @@ describe('CLI index.ts', () => {
421
422
  it('displays clean message when flag is used', async () => {
422
423
  mockFindUntrackedFiles.mockResolvedValue([]);
423
424
  mockStartServer.mockResolvedValue({
424
- port: 3000,
425
- url: 'http://localhost:3000',
425
+ port: 4966,
426
+ url: 'http://localhost:4966',
426
427
  isEmpty: false,
427
428
  });
428
429
  const program = new Command();
@@ -461,8 +462,8 @@ describe('CLI index.ts', () => {
461
462
  it('does not display clean message when flag is not used', async () => {
462
463
  mockFindUntrackedFiles.mockResolvedValue([]);
463
464
  mockStartServer.mockResolvedValue({
464
- port: 3000,
465
- url: 'http://localhost:3000',
465
+ port: 4966,
466
+ url: 'http://localhost:4966',
466
467
  isEmpty: false,
467
468
  });
468
469
  const program = new Command();
@@ -503,8 +504,8 @@ describe('CLI index.ts', () => {
503
504
  it('displays server startup message with correct URL', async () => {
504
505
  mockFindUntrackedFiles.mockResolvedValue([]);
505
506
  mockStartServer.mockResolvedValue({
506
- port: 3000,
507
- url: 'http://localhost:3000',
507
+ port: 4966,
508
+ url: 'http://localhost:4966',
508
509
  isEmpty: false,
509
510
  });
510
511
  const program = new Command();
@@ -540,14 +541,14 @@ describe('CLI index.ts', () => {
540
541
  }
541
542
  });
542
543
  await program.parseAsync([], { from: 'user' });
543
- expect(console.log).toHaveBeenCalledWith('\n🚀 difit server started on http://localhost:3000');
544
+ expect(console.log).toHaveBeenCalledWith('\n🚀 difit server started on http://localhost:4966');
544
545
  expect(console.log).toHaveBeenCalledWith('📋 Reviewing: HEAD');
545
546
  });
546
547
  it('displays correct message when no differences found', async () => {
547
548
  mockFindUntrackedFiles.mockResolvedValue([]);
548
549
  mockStartServer.mockResolvedValue({
549
- port: 3000,
550
- url: 'http://localhost:3000',
550
+ port: 4966,
551
+ url: 'http://localhost:4966',
551
552
  isEmpty: true,
552
553
  });
553
554
  const program = new Command();
@@ -578,7 +579,7 @@ describe('CLI index.ts', () => {
578
579
  });
579
580
  await program.parseAsync([], { from: 'user' });
580
581
  expect(console.log).toHaveBeenCalledWith('\n! No differences found. Browser will not open automatically.');
581
- expect(console.log).toHaveBeenCalledWith(' Server is running at http://localhost:3000 if you want to check manually.\n');
582
+ expect(console.log).toHaveBeenCalledWith(' Server is running at http://localhost:4966 if you want to check manually.\n');
582
583
  });
583
584
  });
584
585
  describe('Server mode option handling', () => {
@@ -812,4 +813,132 @@ describe('CLI index.ts', () => {
812
813
  expect(process.exit).toHaveBeenCalledWith(1);
813
814
  });
814
815
  });
816
+ describe('Diff mode determination', () => {
817
+ const testCases = [
818
+ {
819
+ name: 'determines DEFAULT mode for HEAD',
820
+ args: ['HEAD'],
821
+ expectedMode: 'default',
822
+ },
823
+ {
824
+ name: 'determines WORKING mode for working',
825
+ args: ['working'],
826
+ expectedMode: 'working',
827
+ },
828
+ {
829
+ name: 'determines STAGED mode for staged',
830
+ args: ['staged'],
831
+ expectedMode: 'staged',
832
+ },
833
+ {
834
+ name: 'determines DOT mode for dot argument',
835
+ args: ['.'],
836
+ expectedMode: 'dot',
837
+ },
838
+ {
839
+ name: 'determines SPECIFIC mode for commit comparison',
840
+ args: ['abc123', 'def456'],
841
+ expectedMode: 'specific',
842
+ },
843
+ {
844
+ name: 'determines DEFAULT mode for custom commit',
845
+ args: ['main'],
846
+ expectedMode: 'default',
847
+ },
848
+ ];
849
+ testCases.forEach(({ name, args, expectedMode }) => {
850
+ it(name, async () => {
851
+ mockFindUntrackedFiles.mockResolvedValue([]);
852
+ const program = new Command();
853
+ program
854
+ .argument('[commit-ish]', 'commit-ish', 'HEAD')
855
+ .argument('[compare-with]', 'compare-with')
856
+ .option('--port <port>', 'port', parseInt)
857
+ .option('--host <host>', 'host', '')
858
+ .option('--no-open', 'no-open')
859
+ .option('--mode <mode>', 'mode', 'side-by-side')
860
+ .option('--tui', 'tui')
861
+ .option('--pr <url>', 'pr')
862
+ .action(async (commitish, compareWith, options) => {
863
+ // Simulate determineDiffMode function behavior
864
+ let diffMode;
865
+ if (compareWith && commitish !== 'HEAD') {
866
+ diffMode = DiffMode.SPECIFIC;
867
+ }
868
+ else if (commitish === 'working') {
869
+ diffMode = DiffMode.WORKING;
870
+ }
871
+ else if (commitish === 'staged') {
872
+ diffMode = DiffMode.STAGED;
873
+ }
874
+ else if (commitish === '.') {
875
+ diffMode = DiffMode.DOT;
876
+ }
877
+ else {
878
+ diffMode = DiffMode.DEFAULT;
879
+ }
880
+ await startServer({
881
+ targetCommitish: commitish,
882
+ baseCommitish: compareWith || commitish + '^',
883
+ preferredPort: options.port,
884
+ host: options.host,
885
+ openBrowser: options.open,
886
+ mode: options.mode,
887
+ diffMode,
888
+ });
889
+ });
890
+ await program.parseAsync(args, { from: 'user' });
891
+ expect(mockStartServer).toHaveBeenCalledWith(expect.objectContaining({
892
+ diffMode: expectedMode,
893
+ }));
894
+ });
895
+ });
896
+ it('handles HEAD comparison with different commit', async () => {
897
+ mockFindUntrackedFiles.mockResolvedValue([]);
898
+ const program = new Command();
899
+ program
900
+ .argument('[commit-ish]', 'commit-ish', 'HEAD')
901
+ .argument('[compare-with]', 'compare-with')
902
+ .option('--port <port>', 'port', parseInt)
903
+ .option('--host <host>', 'host', '')
904
+ .option('--no-open', 'no-open')
905
+ .option('--mode <mode>', 'mode', 'side-by-side')
906
+ .option('--tui', 'tui')
907
+ .option('--pr <url>', 'pr')
908
+ .action(async (commitish, compareWith, options) => {
909
+ // Simulate determineDiffMode function behavior
910
+ let diffMode;
911
+ if (compareWith && commitish !== 'HEAD') {
912
+ diffMode = DiffMode.SPECIFIC;
913
+ }
914
+ else if (commitish === 'working') {
915
+ diffMode = DiffMode.WORKING;
916
+ }
917
+ else if (commitish === 'staged') {
918
+ diffMode = DiffMode.STAGED;
919
+ }
920
+ else if (commitish === '.') {
921
+ diffMode = DiffMode.DOT;
922
+ }
923
+ else {
924
+ diffMode = DiffMode.DEFAULT;
925
+ }
926
+ await startServer({
927
+ targetCommitish: commitish,
928
+ baseCommitish: compareWith || commitish + '^',
929
+ preferredPort: options.port,
930
+ host: options.host,
931
+ openBrowser: options.open,
932
+ mode: options.mode,
933
+ diffMode,
934
+ });
935
+ });
936
+ await program.parseAsync(['HEAD', 'main'], { from: 'user' });
937
+ expect(mockStartServer).toHaveBeenCalledWith(expect.objectContaining({
938
+ diffMode: 'default', // HEAD with comparison is still DEFAULT mode
939
+ targetCommitish: 'HEAD',
940
+ baseCommitish: 'main',
941
+ }));
942
+ });
943
+ });
815
944
  });
package/dist/cli/utils.js CHANGED
@@ -21,6 +21,7 @@ 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
+ /^@(~\d+|\^\d*)*$/, // @, @~1, @^, @^2, etc. (@ is Git alias for HEAD)
24
25
  ];
25
26
  // Check if it matches any specific patterns first
26
27
  if (validPatterns.some((pattern) => pattern.test(trimmed))) {
@@ -35,8 +36,7 @@ function isValidBranchName(name) {
35
36
  return false; // Cannot start with dash
36
37
  if (name.endsWith('.'))
37
38
  return false; // Cannot end with dot
38
- if (name === '@')
39
- return false; // Cannot be just @
39
+ // @ is a valid Git alias for HEAD, so we should allow it
40
40
  if (name.includes('..'))
41
41
  return false; // No consecutive dots
42
42
  if (name.includes('@{'))
@@ -27,6 +27,15 @@ describe('CLI Utils', () => {
27
27
  expect(validateCommitish('HEAD^2')).toBe(true);
28
28
  expect(validateCommitish('HEAD~2^1')).toBe(true);
29
29
  });
30
+ it('should validate @ references (Git alias for HEAD)', () => {
31
+ expect(validateCommitish('@')).toBe(true);
32
+ expect(validateCommitish('@~1')).toBe(true);
33
+ expect(validateCommitish('@~10')).toBe(true);
34
+ expect(validateCommitish('@^')).toBe(true);
35
+ expect(validateCommitish('@^1')).toBe(true);
36
+ expect(validateCommitish('@^2')).toBe(true);
37
+ expect(validateCommitish('@~2^1')).toBe(true);
38
+ });
30
39
  it('should validate branch names', () => {
31
40
  // Valid branch names according to git rules
32
41
  expect(validateCommitish('main')).toBe(true);
@@ -56,7 +65,7 @@ describe('CLI Utils', () => {
56
65
  // Invalid branch names according to git rules
57
66
  expect(validateCommitish('-feature')).toBe(false); // cannot start with dash
58
67
  expect(validateCommitish('feature.')).toBe(false); // cannot end with dot
59
- expect(validateCommitish('@')).toBe(false); // cannot be just @
68
+ expect(validateCommitish('@')).toBe(true); // @ is a valid Git alias for HEAD
60
69
  expect(validateCommitish('feature..test')).toBe(false); // no consecutive dots
61
70
  expect(validateCommitish('feature@{upstream}')).toBe(false); // no @{ sequence
62
71
  expect(validateCommitish('feature//test')).toBe(false); // no consecutive slashes