difit 2.0.11 → 2.1.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.
- package/README.ja.md +17 -2
- package/README.ko.md +17 -2
- package/README.md +17 -2
- package/README.zh.md +17 -2
- package/dist/cli/index.js +50 -0
- package/dist/cli/index.test.js +141 -12
- package/dist/cli/utils.js +2 -2
- package/dist/cli/utils.test.js +10 -1
- package/dist/client/assets/index-BtavrLIu.css +1 -0
- package/dist/client/assets/index-Bx2n4Aep.js +210 -0
- package/dist/client/assets/{prism-csharp-Dc46Fjt0.js → prism-csharp-BTkEzOdP.js} +1 -1
- package/dist/client/assets/{prism-java-CqBdPW_L.js → prism-java-B6gV82l4.js} +1 -1
- package/dist/client/assets/{prism-php-BLhwjsTl.js → prism-php-gnpy0VQF.js} +1 -1
- package/dist/client/assets/prism-protobuf-DiQ_z8B5.js +1 -0
- package/dist/client/assets/{prism-ruby-ExhPumJe.js → prism-ruby-CMkpRodx.js} +1 -1
- package/dist/client/assets/{prism-solidity-BCgmGzF-.js → prism-solidity-BDXCWkss.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/file-watcher.d.ts +23 -0
- package/dist/server/file-watcher.js +236 -0
- package/dist/server/file-watcher.test.d.ts +1 -0
- package/dist/server/file-watcher.test.js +225 -0
- package/dist/server/git-diff.d.ts +2 -0
- package/dist/server/git-diff.js +47 -4
- package/dist/server/git-diff.test.js +209 -0
- package/dist/server/server.d.ts +5 -2
- package/dist/server/server.js +66 -16
- package/dist/server/server.test.js +3 -3
- package/dist/types/watch.d.ts +30 -0
- package/dist/types/watch.js +8 -0
- package/package.json +3 -1
- package/dist/client/assets/index-B6vRltPu.css +0 -1
- package/dist/client/assets/index-CjocZrF8.js +0 -200
package/README.ja.md
CHANGED
|
@@ -41,7 +41,7 @@ npx difit feature # featureブランチの最新コミット
|
|
|
41
41
|
### 2つのコミットを比較
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
npx difit
|
|
44
|
+
npx difit @ main # mainブランチと比較(@はHEADのエイリアス)
|
|
45
45
|
npx difit feature main # ブランチ間を比較
|
|
46
46
|
npx difit . origin/main # 作業ディレクトリとリモートmainを比較
|
|
47
47
|
```
|
|
@@ -76,6 +76,21 @@ Enterprise ServerのPRを表示する場合、あなたのEnterprise Serverイ
|
|
|
76
76
|
2. 適切なスコープでパーソナルアクセストークンを生成
|
|
77
77
|
3. `GITHUB_TOKEN`環境変数として設定
|
|
78
78
|
|
|
79
|
+
### 標準入力
|
|
80
|
+
|
|
81
|
+
パイプを使用して標準入力経由で統一diff形式を渡すことで、任意のツールからのdiffをdifitで表示できます。
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 他のツールからのdiffを表示
|
|
85
|
+
diff -u file1.txt file2.txt | npx difit
|
|
86
|
+
|
|
87
|
+
# 保存されたパッチをレビュー
|
|
88
|
+
cat changes.patch | npx difit
|
|
89
|
+
|
|
90
|
+
# マージベースとの比較
|
|
91
|
+
git diff --merge-base main feature | npx difit
|
|
92
|
+
```
|
|
93
|
+
|
|
79
94
|
## ⚙️ CLIオプション
|
|
80
95
|
|
|
81
96
|
| フラグ | デフォルト | 説明 |
|
|
@@ -83,7 +98,7 @@ Enterprise ServerのPRを表示する場合、あなたのEnterprise Serverイ
|
|
|
83
98
|
| `<target>` | HEAD | コミットハッシュ、タグ、HEAD~n、ブランチ、または特別な引数 |
|
|
84
99
|
| `[compare-with]` | - | 比較対象の2番目のコミット(2つの間のdiffを表示) |
|
|
85
100
|
| `--pr <url>` | - | レビューするGitHub PRのURL(例:https://github.com/owner/repo/pull/123) |
|
|
86
|
-
| `--port` |
|
|
101
|
+
| `--port` | 4966 | 優先ポート。使用中の場合は+1にフォールバック |
|
|
87
102
|
| `--host` | 127.0.0.1 | サーバーをバインドするホストアドレス(外部からアクセスしたい場合は0.0.0.0を指定) |
|
|
88
103
|
| `--no-open` | false | ブラウザを自動的に開かない |
|
|
89
104
|
| `--mode` | side-by-side | 表示モード。inline`または`side-by-side` |
|
package/README.ko.md
CHANGED
|
@@ -41,7 +41,7 @@ npx difit feature # feature 브랜치의 최신 커밋
|
|
|
41
41
|
### 두 커밋 비교
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
npx difit
|
|
44
|
+
npx difit @ main # main 브랜치와 비교 (@는 HEAD의 별칭)
|
|
45
45
|
npx difit feature main # 브랜치 간 비교
|
|
46
46
|
npx difit . origin/main # 작업 디렉토리와 원격 main 비교
|
|
47
47
|
```
|
|
@@ -76,6 +76,21 @@ Enterprise Server PR의 경우 귀하의 Enterprise Server 인스턴스에서
|
|
|
76
76
|
2. 적절한 범위로 개인 액세스 토큰 생성
|
|
77
77
|
3. `GITHUB_TOKEN` 환경 변수로 설정
|
|
78
78
|
|
|
79
|
+
### 표준 입력
|
|
80
|
+
|
|
81
|
+
파이프를 사용하여 표준 입력을 통해 통합 diff를 전달하면 모든 도구의 diff를 difit으로 볼 수 있습니다.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 다른 도구의 diff 보기
|
|
85
|
+
diff -u file1.txt file2.txt | npx difit
|
|
86
|
+
|
|
87
|
+
# 저장된 패치 검토
|
|
88
|
+
cat changes.patch | npx difit
|
|
89
|
+
|
|
90
|
+
# 머지 베이스와 비교
|
|
91
|
+
git diff --merge-base main feature | npx difit
|
|
92
|
+
```
|
|
93
|
+
|
|
79
94
|
## ⚙️ CLI 옵션
|
|
80
95
|
|
|
81
96
|
| 플래그 | 기본값 | 설명 |
|
|
@@ -83,7 +98,7 @@ Enterprise Server PR의 경우 귀하의 Enterprise Server 인스턴스에서
|
|
|
83
98
|
| `<target>` | HEAD | 커밋 해시, 태그, HEAD~n, 브랜치 또는 특수 인수 |
|
|
84
99
|
| `[compare-with]` | - | 비교할 선택적 두 번째 커밋 (둘 사이의 diff 표시) |
|
|
85
100
|
| `--pr <url>` | - | 검토할 GitHub PR URL (예: https://github.com/owner/repo/pull/123) |
|
|
86
|
-
| `--port` |
|
|
101
|
+
| `--port` | 4966 | 선호 포트; 사용 중인 경우 +1로 대체 |
|
|
87
102
|
| `--host` | 127.0.0.1 | 서버를 바인딩할 호스트 주소 (외부 액세스는 0.0.0.0 사용) |
|
|
88
103
|
| `--no-open` | false | 브라우저를 자동으로 열지 않음 |
|
|
89
104
|
| `--mode` | side-by-side | 표시 모드: `inline` 또는 `side-by-side` |
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npx difit feature # Latest commit on feature branch
|
|
|
41
41
|
### Compare two commits
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
npx difit
|
|
44
|
+
npx difit @ main # Compare with main branch (@ is alias for HEAD)
|
|
45
45
|
npx difit feature main # Compare branches
|
|
46
46
|
npx difit . origin/main # Compare working directory with remote main
|
|
47
47
|
```
|
|
@@ -76,6 +76,21 @@ For Enterprise Server PRs, you must set a token generated on YOUR Enterprise Ser
|
|
|
76
76
|
2. Generate a personal access token with appropriate scopes
|
|
77
77
|
3. Set it as `GITHUB_TOKEN` environment variable
|
|
78
78
|
|
|
79
|
+
### Stdin
|
|
80
|
+
|
|
81
|
+
By using a pipe to pass unified diffs via stdin, you can view diffs from any tool with difit.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# View diffs from other tools
|
|
85
|
+
diff -u file1.txt file2.txt | npx difit
|
|
86
|
+
|
|
87
|
+
# Review saved patches
|
|
88
|
+
cat changes.patch | npx difit
|
|
89
|
+
|
|
90
|
+
# Compare against merge base
|
|
91
|
+
git diff --merge-base main feature | npx difit
|
|
92
|
+
```
|
|
93
|
+
|
|
79
94
|
## ⚙️ CLI Options
|
|
80
95
|
|
|
81
96
|
| Flag | Default | Description |
|
|
@@ -83,7 +98,7 @@ For Enterprise Server PRs, you must set a token generated on YOUR Enterprise Ser
|
|
|
83
98
|
| `<target>` | HEAD | Commit hash, tag, HEAD~n, branch, or special arguments |
|
|
84
99
|
| `[compare-with]` | - | Optional second commit to compare with (shows diff between the two) |
|
|
85
100
|
| `--pr <url>` | - | GitHub PR URL to review (e.g., https://github.com/owner/repo/pull/123) |
|
|
86
|
-
| `--port` |
|
|
101
|
+
| `--port` | 4966 | Preferred port; falls back to +1 if occupied |
|
|
87
102
|
| `--host` | 127.0.0.1 | Host address to bind server to (use 0.0.0.0 for external access) |
|
|
88
103
|
| `--no-open` | false | Don't automatically open browser |
|
|
89
104
|
| `--mode` | side-by-side | Display mode: `inline` or `side-by-side` |
|
package/README.zh.md
CHANGED
|
@@ -41,7 +41,7 @@ npx difit feature # feature 分支上的最新提交
|
|
|
41
41
|
### 比较两个提交
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
npx difit
|
|
44
|
+
npx difit @ main # 与 main 分支比较(@ 是 HEAD 的别名)
|
|
45
45
|
npx difit feature main # 比较分支
|
|
46
46
|
npx difit . origin/main # 比较工作目录与远程 main
|
|
47
47
|
```
|
|
@@ -76,6 +76,21 @@ difit 使用以下方式自动处理 GitHub 认证:
|
|
|
76
76
|
2. 生成具有适当范围的个人访问令牌
|
|
77
77
|
3. 将其设置为 `GITHUB_TOKEN` 环境变量
|
|
78
78
|
|
|
79
|
+
### 标准输入
|
|
80
|
+
|
|
81
|
+
通过使用管道通过标准输入传递统一差异,您可以使用 difit 查看来自任何工具的差异。
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 查看来自其他工具的差异
|
|
85
|
+
diff -u file1.txt file2.txt | npx difit
|
|
86
|
+
|
|
87
|
+
# 审查保存的补丁
|
|
88
|
+
cat changes.patch | npx difit
|
|
89
|
+
|
|
90
|
+
# 与合并基础比较
|
|
91
|
+
git diff --merge-base main feature | npx difit
|
|
92
|
+
```
|
|
93
|
+
|
|
79
94
|
## ⚙️ CLI 选项
|
|
80
95
|
|
|
81
96
|
| 标志 | 默认值 | 描述 |
|
|
@@ -83,7 +98,7 @@ difit 使用以下方式自动处理 GitHub 认证:
|
|
|
83
98
|
| `<target>` | HEAD | 提交哈希、标签、HEAD~n、分支或特殊参数 |
|
|
84
99
|
| `[compare-with]` | - | 要比较的可选第二个提交(显示两者之间的差异) |
|
|
85
100
|
| `--pr <url>` | - | 要审查的 GitHub PR URL(例如:https://github.com/owner/repo/pull/123) |
|
|
86
|
-
| `--port` |
|
|
101
|
+
| `--port` | 4966 | 首选端口;如果被占用则回退到 +1 |
|
|
87
102
|
| `--host` | 127.0.0.1 | 绑定服务器的主机地址(使用 0.0.0.0 进行外部访问) |
|
|
88
103
|
| `--no-open` | false | 不自动打开浏览器 |
|
|
89
104
|
| `--mode` | side-by-side | 显示模式:`inline` 或 `side-by-side` |
|
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) {
|
package/dist/cli/index.test.js
CHANGED
|
@@ -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:
|
|
41
|
-
url: 'http://localhost:
|
|
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:
|
|
425
|
-
url: 'http://localhost:
|
|
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:
|
|
465
|
-
url: 'http://localhost:
|
|
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:
|
|
507
|
-
url: 'http://localhost:
|
|
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:
|
|
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:
|
|
550
|
-
url: 'http://localhost:
|
|
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:
|
|
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
|
-
|
|
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('@{'))
|
package/dist/cli/utils.test.js
CHANGED
|
@@ -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(
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-100:oklch(93.6% .032 17.717);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-600:oklch(68.1% .162 75.834);--color-green-100:oklch(96.2% .044 156.743);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--radius-md:.375rem;--radius-lg:.5rem;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-github-bg-primary:#0d1117;--color-github-bg-secondary:#161b22;--color-github-bg-tertiary:#21262d;--color-github-border:#30363d;--color-github-text-primary:#f0f6fc;--color-github-text-secondary:#8b949e;--color-github-text-muted:#6e7681;--color-github-accent:#238636;--color-github-danger:#da3633;--color-github-warning:#d29922;--color-diff-addition-bg:#0d4429;--color-diff-addition-border:#1b7c3d;--color-diff-deletion-bg:#67060c;--color-diff-deletion-border:#da3633;--color-diff-neutral-bg:#21262d;--color-diff-selected-bg:#ae7c1426;--color-diff-selected-border:#ae7c1466;--color-comment-bg:#1c2128;--color-comment-border:#373e47;--color-comment-text:#e6edf3}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.-right-2{right:calc(var(--spacing)*-2)}.right-0{right:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.left-3{left:calc(var(--spacing)*3)}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-7{height:calc(var(--spacing)*7)}.h-full{height:100%}.h-screen{height:100vh}.max-h-96{max-height:calc(var(--spacing)*96)}.max-h-\[80vh\]{max-height:80vh}.max-h-\[calc\(80vh-120px\)\]{max-height:calc(80vh - 120px)}.min-h-\[16px\]{min-height:16px}.min-h-\[20px\]{min-height:20px}.min-h-\[60px\]{min-height:60px}.w-1\/2{width:50%}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-7{width:calc(var(--spacing)*7)}.w-8{width:calc(var(--spacing)*8)}.w-\[50px\]{width:50px}.w-\[60px\]{width:60px}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:calc(var(--spacing)*0)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-none{--tw-border-style:none;border-style:none}.border-\[var\(--border-muted\)\]{border-color:var(--border-muted)}.border-github-accent{border-color:var(--color-github-accent)}.border-github-border{border-color:var(--color-github-border)}.border-github-text-muted{border-color:var(--color-github-text-muted)}.border-orange-800{border-color:var(--color-orange-800)}.border-yellow-600\/50{border-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.border-yellow-600\/50{border-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.border-t-github-accent{border-top-color:var(--color-github-accent)}.border-l-yellow-400{border-left-color:var(--color-yellow-400)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-diff-addition-bg{background-color:var(--color-diff-addition-bg)}.bg-diff-deletion-bg{background-color:var(--color-diff-deletion-bg)}.bg-github-accent{background-color:var(--color-github-accent)}.bg-github-bg-primary{background-color:var(--color-github-bg-primary)}.bg-github-bg-secondary{background-color:var(--color-github-bg-secondary)}.bg-github-bg-tertiary{background-color:var(--color-github-bg-tertiary)}.bg-github-border{background-color:var(--color-github-border)}.bg-green-100\/10{background-color:#dcfce71a}@supports (color:color-mix(in lab,red,red)){.bg-green-100\/10{background-color:color-mix(in oklab,var(--color-green-100)10%,transparent)}}.bg-orange-700{background-color:var(--color-orange-700)}.bg-red-100\/10{background-color:#ffe2e21a}@supports (color:color-mix(in lab,red,red)){.bg-red-100\/10{background-color:color-mix(in oklab,var(--color-red-100)10%,transparent)}}.bg-transparent{background-color:#0000}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-4{padding-top:calc(var(--spacing)*4)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-5{padding-right:calc(var(--spacing)*5)}.pb-px{padding-bottom:1px}.pl-9{padding-left:calc(var(--spacing)*9)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-github-accent{color:var(--color-github-accent)}.text-github-danger{color:var(--color-github-danger)}.text-github-text-muted{color:var(--color-github-text-muted)}.text-github-text-primary{color:var(--color-github-text-primary)}.text-github-text-secondary{color:var(--color-github-text-secondary)}.text-github-warning{color:var(--color-github-warning)}.text-green-600{color:var(--color-green-600)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.placeholder-github-text-muted::-moz-placeholder{color:var(--color-github-text-muted)}.placeholder-github-text-muted::placeholder{color:var(--color-github-text-muted)}.accent-github-accent{accent-color:var(--color-github-accent)}.opacity-70{opacity:.7}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.\!transition-all{transition-property:all!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\!duration-300{--tw-duration:.3s!important;transition-duration:.3s!important}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.\!ease-in-out{--tw-ease:var(--ease-in-out)!important;transition-timing-function:var(--ease-in-out)!important}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.select-text{-webkit-user-select:text;-moz-user-select:text;user-select:text}.after\:pointer-events-none:after{content:var(--tw-content);pointer-events:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-0:after{content:var(--tw-content);inset:calc(var(--spacing)*0)}.after\:border-t-2:after{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:2px}.after\:border-b-2:after{content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.after\:border-l-4:after{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:4px}.after\:border-l-5:after{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:5px}.after\:border-blue-500:after{content:var(--tw-content);border-color:var(--color-blue-500)}.after\:border-l-diff-selected-border:after{content:var(--tw-content);border-left-color:var(--color-diff-selected-border)}.after\:bg-blue-100:after{content:var(--tw-content);background-color:var(--color-blue-100)}.after\:bg-diff-selected-bg:after{content:var(--tw-content);background-color:var(--color-diff-selected-bg)}.after\:opacity-30:after{content:var(--tw-content);opacity:.3}@media (hover:hover){.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-github-accent\/50:hover{border-color:#23863680}@supports (color:color-mix(in lab,red,red)){.hover\:border-github-accent\/50:hover{border-color:color-mix(in oklab,var(--color-github-accent)50%,transparent)}}.hover\:border-github-text-muted:hover{border-color:var(--color-github-text-muted)}.hover\:border-green-600:hover{border-color:var(--color-green-600)}.hover\:border-orange-900:hover{border-color:var(--color-orange-900)}.hover\:bg-github-bg-primary:hover{background-color:var(--color-github-bg-primary)}.hover\:bg-github-bg-tertiary:hover{background-color:var(--color-github-bg-tertiary)}.hover\:bg-github-text-muted:hover{background-color:var(--color-github-text-muted)}.hover\:bg-green-500\/10:hover{background-color:#00c7581a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-green-500\/10:hover{background-color:color-mix(in oklab,var(--color-green-500)10%,transparent)}}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.hover\:text-github-text-primary:hover{color:var(--color-github-text-primary)}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:min-h-\[80px\]:focus{min-height:80px}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-github-accent:focus{border-color:var(--color-github-accent)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-600\/30:focus{--tw-ring-color:#155dfc4d}@supports (color:color-mix(in lab,red,red)){.focus\:ring-blue-600\/30:focus{--tw-ring-color:color-mix(in oklab,var(--color-blue-600)30%,transparent)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:border-slate-500{border-color:var(--color-slate-500)}.dark\:bg-slate-600{background-color:var(--color-slate-600)}.dark\:text-white{color:var(--color-white)}@media (hover:hover){.dark\:hover\:border-slate-400:hover{border-color:var(--color-slate-400)}.dark\:hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}}}.\[\&_code\]\:\!bg-transparent code{background-color:#0000!important}.\[\&_code\]\:text-inherit code{color:inherit}.\[\&_pre\]\:m-0 pre{margin:calc(var(--spacing)*0)}.\[\&_pre\]\:\!bg-transparent pre{background-color:#0000!important}.\[\&_pre\]\:p-0 pre{padding:calc(var(--spacing)*0)}.\[\&_pre\]\:text-inherit pre{color:inherit}}:root{--app-font-size:14px;--app-font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif}html,body{background-color:var(--color-github-bg-primary);color:var(--color-github-text-primary);font-family:var(--app-font-family);line-height:1.5;font-size:var(--app-font-size)}:root{interpolate-size:allow-keywords}button{cursor:pointer}@keyframes sparkle-rise{0%{opacity:0;transform:translateY(20px)scale(.5)}20%{opacity:1;transform:translateY(10px)scale(1)}80%{opacity:1;transform:translateY(-30px)scale(1)}to{opacity:0;transform:translateY(-40px)scale(.8)}}.animate-sparkle-rise{animation:.8s ease-out both sparkle-rise}html,body,.bg-github-bg-primary,.bg-github-bg-secondary,.bg-github-bg-tertiary,[class*=bg-github],[class*=text-github],[class*=border-github],[class*=bg-diff],[class*=border-diff]{transition:background-color .3s,color .3s,border-color .3s}.keyboard-cursor{outline-offset:-2px;outline:2px solid #4d7adb}.word-token{cursor:pointer;transition:background-color .15s;position:relative}.word-highlight{background-color:var(--word-highlight-color,#fffd54);border-radius:2px}[data-theme=dark]{--word-highlight-color:#fffd544d}[data-theme=light]{--word-highlight-color:#fffd54}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}
|