difit 2.0.9 → 2.0.11
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 +177 -0
- package/README.ko.md +177 -0
- package/README.md +46 -44
- package/README.zh.md +177 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.test.js +142 -0
- package/dist/cli/utils.d.ts +1 -0
- package/dist/cli/utils.js +27 -8
- package/dist/cli/utils.test.js +20 -1
- package/dist/client/assets/index-B6vRltPu.css +1 -0
- package/dist/client/assets/index-CjocZrF8.js +200 -0
- package/dist/client/assets/prism-csharp-Dc46Fjt0.js +1 -0
- package/dist/client/assets/{prism-java-D-0IYQPf.js → prism-java-CqBdPW_L.js} +1 -1
- package/dist/client/assets/{prism-php-DlgOuveU.js → prism-php-BLhwjsTl.js} +1 -1
- package/dist/client/assets/{prism-ruby--gqFanLO.js → prism-ruby-ExhPumJe.js} +1 -1
- package/dist/client/assets/{prism-solidity-B-E0RvdV.js → prism-solidity-BCgmGzF-.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/server.d.ts +1 -0
- package/dist/server/server.js +1 -0
- package/dist/server/server.test.js +41 -0
- package/dist/tui/App.js +3 -5
- package/dist/tui/components/DiffViewer.d.ts +0 -1
- package/dist/types/diff.d.ts +1 -0
- package/package.json +1 -2
- package/dist/client/assets/index-BrZ_uVsY.css +0 -1
- package/dist/client/assets/index-zJy7Hgz-.js +0 -195
package/README.zh.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="public/logo.png" alt="difit" width="260">
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="./README.md">English</a> | <a href="./README.ja.md">日本語</a> | 简体中文 | <a href="./README.ko.md">한국어</a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
**difit** 是一个让你使用 GitHub 风格查看器查看和审查本地 git 差异的 CLI 工具。除了清晰的视觉效果外,评论还可以作为 AI 提示进行复制。AI 时代的本地代码审查工具!
|
|
10
|
+
|
|
11
|
+
## ✨ 功能
|
|
12
|
+
|
|
13
|
+
- ⚡ **零配置**:只需运行 `npx difit` 即可使用
|
|
14
|
+
- 💬 **本地审查**:为差异添加评论,并将其与文件路径和行号一起复制给 AI
|
|
15
|
+
- 🖥️ **WebUI/终端UI**:在浏览器中使用 Web UI,或使用 `--tui` 保持在终端中
|
|
16
|
+
|
|
17
|
+
## ⚡ 快速开始
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx difit # 在 WebUI 中查看最新提交的差异
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 🚀 使用方法
|
|
24
|
+
|
|
25
|
+
### 基本用法
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx difit <target> # 查看单个提交差异
|
|
29
|
+
npx difit <target> [compare-with] # 比较两个提交/分支
|
|
30
|
+
npx difit --pr <github-pr-url> # 审查 GitHub 拉取请求
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 单个提交审查
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx difit # HEAD(最新)提交
|
|
37
|
+
npx difit 6f4a9b7 # 特定提交
|
|
38
|
+
npx difit feature # feature 分支上的最新提交
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 比较两个提交
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx difit HEAD main # 比较 HEAD 与 main 分支
|
|
45
|
+
npx difit feature main # 比较分支
|
|
46
|
+
npx difit . origin/main # 比较工作目录与远程 main
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 特殊参数
|
|
50
|
+
|
|
51
|
+
difit 支持常见差异场景的特殊关键字:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx difit . # 所有未提交的更改(暂存区 + 未暂存)
|
|
55
|
+
npx difit staged # 暂存区更改
|
|
56
|
+
npx difit working # 仅未暂存的更改
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### GitHub PR
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx difit --pr https://github.com/owner/repo/pull/123
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
difit 使用以下方式自动处理 GitHub 认证:
|
|
66
|
+
|
|
67
|
+
1. **GitHub CLI**(推荐):如果您已使用 `gh auth login` 登录,difit 将使用您现有的凭据
|
|
68
|
+
2. **环境变量**:设置 `GITHUB_TOKEN` 环境变量
|
|
69
|
+
3. **无认证**:公共仓库无需认证即可工作(有速率限制)
|
|
70
|
+
|
|
71
|
+
#### GitHub Enterprise Server
|
|
72
|
+
|
|
73
|
+
对于 Enterprise Server PR,您必须设置在您的 Enterprise Server 实例上生成的令牌:
|
|
74
|
+
|
|
75
|
+
1. 转到 `https://YOUR-ENTERPRISE-SERVER/settings/tokens`
|
|
76
|
+
2. 生成具有适当范围的个人访问令牌
|
|
77
|
+
3. 将其设置为 `GITHUB_TOKEN` 环境变量
|
|
78
|
+
|
|
79
|
+
## ⚙️ CLI 选项
|
|
80
|
+
|
|
81
|
+
| 标志 | 默认值 | 描述 |
|
|
82
|
+
| ---------------- | ------------ | ---------------------------------------------------------------------- |
|
|
83
|
+
| `<target>` | HEAD | 提交哈希、标签、HEAD~n、分支或特殊参数 |
|
|
84
|
+
| `[compare-with]` | - | 要比较的可选第二个提交(显示两者之间的差异) |
|
|
85
|
+
| `--pr <url>` | - | 要审查的 GitHub PR URL(例如:https://github.com/owner/repo/pull/123) |
|
|
86
|
+
| `--port` | 3000 | 首选端口;如果被占用则回退到 +1 |
|
|
87
|
+
| `--host` | 127.0.0.1 | 绑定服务器的主机地址(使用 0.0.0.0 进行外部访问) |
|
|
88
|
+
| `--no-open` | false | 不自动打开浏览器 |
|
|
89
|
+
| `--mode` | side-by-side | 显示模式:`inline` 或 `side-by-side` |
|
|
90
|
+
| `--tui` | false | 使用终端 UI 模式而不是 WebUI |
|
|
91
|
+
| `--clean` | false | 启动时清除所有现有评论 |
|
|
92
|
+
|
|
93
|
+
## 💬 评论系统
|
|
94
|
+
|
|
95
|
+
difit 包含一个审查评论系统,便于向 AI 编码代理提供反馈:
|
|
96
|
+
|
|
97
|
+
1. **添加评论**:单击任何差异行上的评论按钮或拖动选择范围
|
|
98
|
+
2. **编辑评论**:使用编辑按钮编辑现有评论
|
|
99
|
+
3. **生成提示**:评论包含"复制提示"按钮,可为 AI 编码代理格式化上下文
|
|
100
|
+
4. **复制全部**:使用"复制所有提示"以结构化格式复制所有评论
|
|
101
|
+
5. **持久存储**:评论按每个提交保存在浏览器 localStorage 中
|
|
102
|
+
|
|
103
|
+
### 评论提示格式
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
src/components/Button.tsx:L42 # 此行自动添加
|
|
107
|
+
使此变量名更具描述性
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
对于范围选择:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
src/components/Button.tsx:L42-L48 # 此行自动添加
|
|
114
|
+
此部分是不必要的
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 🎨 语法高亮语言
|
|
118
|
+
|
|
119
|
+
- **JavaScript/TypeScript**:`.js`、`.jsx`、`.ts`、`.tsx`
|
|
120
|
+
- **Web 技术**:HTML、CSS、JSON、XML、Markdown
|
|
121
|
+
- **Shell 脚本**:`.sh`、`.bash`、`.zsh`、`.fish`
|
|
122
|
+
- **后端语言**:PHP、SQL、Ruby、Java、Scala
|
|
123
|
+
- **系统语言**:C、C++、C#、Rust、Go
|
|
124
|
+
- **移动语言**:Swift、Kotlin、Dart
|
|
125
|
+
- **其他**:Python、YAML、Solidity、Vim 脚本
|
|
126
|
+
|
|
127
|
+
## 🛠️ 开发
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# 安装依赖
|
|
131
|
+
pnpm install
|
|
132
|
+
|
|
133
|
+
# 启动开发服务器(带热重载)
|
|
134
|
+
# 这会同时运行 Vite 开发服务器和 CLI,NODE_ENV=development
|
|
135
|
+
pnpm run dev
|
|
136
|
+
|
|
137
|
+
# 构建并启动生产服务器
|
|
138
|
+
pnpm run start <target>
|
|
139
|
+
|
|
140
|
+
# 构建生产版本
|
|
141
|
+
pnpm run build
|
|
142
|
+
|
|
143
|
+
# 运行测试
|
|
144
|
+
pnpm test
|
|
145
|
+
|
|
146
|
+
# 代码检查和格式化
|
|
147
|
+
pnpm run lint
|
|
148
|
+
pnpm run format
|
|
149
|
+
pnpm run typecheck
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 开发工作流程
|
|
153
|
+
|
|
154
|
+
- **`pnpm run dev`**:同时启动 Vite 开发服务器(带热重载)和 CLI 服务器
|
|
155
|
+
- **`pnpm run start <target>`**:构建所有内容并启动生产服务器(用于测试最终构建)
|
|
156
|
+
- **开发模式**:使用 Vite 的开发服务器进行热重载和快速开发
|
|
157
|
+
- **生产模式**:提供构建的静态文件(供 npx 和生产构建使用)
|
|
158
|
+
|
|
159
|
+
## 🏗️ 架构
|
|
160
|
+
|
|
161
|
+
- **CLI**:使用 Commander.js 进行参数解析,具有全面的验证
|
|
162
|
+
- **后端**:Express 服务器配合 simple-git 进行差异处理
|
|
163
|
+
- **GitHub 集成**:Octokit 用于 GitHub API,具有自动认证(GitHub CLI + 环境变量)
|
|
164
|
+
- **前端**:React 18 + TypeScript + Vite
|
|
165
|
+
- **样式**:Tailwind CSS v4,带有类似 GitHub 的深色主题
|
|
166
|
+
- **语法高亮**:Prism.js 带动态语言加载
|
|
167
|
+
- **测试**:Vitest 用于单元测试,测试文件与源代码放在一起
|
|
168
|
+
- **质量**:ESLint、Prettier、lefthook 预提交钩子
|
|
169
|
+
|
|
170
|
+
## 📋 要求
|
|
171
|
+
|
|
172
|
+
- Node.js ≥ 21.0.0
|
|
173
|
+
- 包含要审查的提交的 Git 仓库
|
|
174
|
+
|
|
175
|
+
## 📄 许可证
|
|
176
|
+
|
|
177
|
+
MIT
|
package/dist/cli/index.js
CHANGED
|
@@ -21,6 +21,7 @@ program
|
|
|
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')
|
|
23
23
|
.option('--pr <url>', 'GitHub PR URL to review (e.g., https://github.com/owner/repo/pull/123)')
|
|
24
|
+
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
24
25
|
.action(async (commitish, compareWith, options) => {
|
|
25
26
|
try {
|
|
26
27
|
// Determine target and base commitish
|
|
@@ -94,9 +95,13 @@ program
|
|
|
94
95
|
host: options.host,
|
|
95
96
|
openBrowser: options.open,
|
|
96
97
|
mode: options.mode,
|
|
98
|
+
clearComments: options.clean,
|
|
97
99
|
});
|
|
98
100
|
console.log(`\n🚀 difit server started on ${url}`);
|
|
99
101
|
console.log(`📋 Reviewing: ${targetCommitish}`);
|
|
102
|
+
if (options.clean) {
|
|
103
|
+
console.log('🧹 Starting with a clean slate - all existing comments will be cleared');
|
|
104
|
+
}
|
|
100
105
|
if (isEmpty) {
|
|
101
106
|
console.log('\n! \x1b[33mNo differences found. Browser will not open automatically.\x1b[0m');
|
|
102
107
|
console.log(` Server is running at ${url} if you want to check manually.\n`);
|
package/dist/cli/index.test.js
CHANGED
|
@@ -177,6 +177,11 @@ describe('CLI index.ts', () => {
|
|
|
177
177
|
args: ['--mode', 'inline'],
|
|
178
178
|
expectedOptions: { mode: 'inline' },
|
|
179
179
|
},
|
|
180
|
+
{
|
|
181
|
+
name: '--clean option',
|
|
182
|
+
args: ['--clean'],
|
|
183
|
+
expectedOptions: { clean: true },
|
|
184
|
+
},
|
|
180
185
|
])('$name', async ({ args, expectedOptions }) => {
|
|
181
186
|
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
182
187
|
const program = new Command();
|
|
@@ -189,6 +194,7 @@ describe('CLI index.ts', () => {
|
|
|
189
194
|
.option('--mode <mode>', 'mode', 'side-by-side')
|
|
190
195
|
.option('--tui', 'tui')
|
|
191
196
|
.option('--pr <url>', 'pr')
|
|
197
|
+
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
192
198
|
.action(async (commitish, _compareWith, options) => {
|
|
193
199
|
let targetCommitish = commitish;
|
|
194
200
|
let baseCommitish = commitish + '^';
|
|
@@ -199,6 +205,7 @@ describe('CLI index.ts', () => {
|
|
|
199
205
|
host: options.host,
|
|
200
206
|
openBrowser: options.open,
|
|
201
207
|
mode: options.mode,
|
|
208
|
+
clearComments: options.clean,
|
|
202
209
|
});
|
|
203
210
|
});
|
|
204
211
|
await program.parseAsync([...args], { from: 'user' });
|
|
@@ -209,6 +216,7 @@ describe('CLI index.ts', () => {
|
|
|
209
216
|
host: expectedOptions.host || '',
|
|
210
217
|
openBrowser: expectedOptions.open !== false,
|
|
211
218
|
mode: expectedOptions.mode || 'side-by-side',
|
|
219
|
+
clearComments: expectedOptions.clean,
|
|
212
220
|
};
|
|
213
221
|
expect(mockStartServer).toHaveBeenCalledWith(expectedCall);
|
|
214
222
|
});
|
|
@@ -356,6 +364,140 @@ describe('CLI index.ts', () => {
|
|
|
356
364
|
expect(console.error).toHaveBeenCalledWith('Error: --pr option cannot be used with positional arguments');
|
|
357
365
|
expect(process.exit).toHaveBeenCalledWith(1);
|
|
358
366
|
});
|
|
367
|
+
it('resolves GitHub Enterprise PR commits correctly', async () => {
|
|
368
|
+
const prUrl = 'https://github.enterprise.com/owner/repo/pull/456';
|
|
369
|
+
const prCommits = {
|
|
370
|
+
targetCommitish: 'xyz789',
|
|
371
|
+
baseCommitish: 'uvw012',
|
|
372
|
+
};
|
|
373
|
+
mockResolvePrCommits.mockResolvedValue(prCommits);
|
|
374
|
+
const program = new Command();
|
|
375
|
+
program
|
|
376
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
377
|
+
.argument('[compare-with]', 'compare-with')
|
|
378
|
+
.option('--port <port>', 'port', parseInt)
|
|
379
|
+
.option('--host <host>', 'host', '')
|
|
380
|
+
.option('--no-open', 'no-open')
|
|
381
|
+
.option('--mode <mode>', 'mode', 'side-by-side')
|
|
382
|
+
.option('--tui', 'tui')
|
|
383
|
+
.option('--pr <url>', 'pr')
|
|
384
|
+
.action(async (commitish, _compareWith, options) => {
|
|
385
|
+
let targetCommitish = commitish;
|
|
386
|
+
let baseCommitish;
|
|
387
|
+
if (options.pr) {
|
|
388
|
+
if (commitish !== 'HEAD' || _compareWith) {
|
|
389
|
+
console.error('Error: --pr option cannot be used with positional arguments');
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
const prCommits = await resolvePrCommits(options.pr);
|
|
393
|
+
targetCommitish = prCommits.targetCommitish;
|
|
394
|
+
baseCommitish = prCommits.baseCommitish;
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
baseCommitish = commitish + '^';
|
|
398
|
+
}
|
|
399
|
+
await startServer({
|
|
400
|
+
targetCommitish,
|
|
401
|
+
baseCommitish,
|
|
402
|
+
preferredPort: options.port,
|
|
403
|
+
host: options.host,
|
|
404
|
+
openBrowser: options.open,
|
|
405
|
+
mode: options.mode,
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
await program.parseAsync(['--pr', prUrl], { from: 'user' });
|
|
409
|
+
expect(mockResolvePrCommits).toHaveBeenCalledWith(prUrl);
|
|
410
|
+
expect(mockStartServer).toHaveBeenCalledWith({
|
|
411
|
+
targetCommitish: 'xyz789',
|
|
412
|
+
baseCommitish: 'uvw012',
|
|
413
|
+
preferredPort: undefined,
|
|
414
|
+
host: '',
|
|
415
|
+
openBrowser: true,
|
|
416
|
+
mode: 'side-by-side',
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
describe('Clean flag functionality', () => {
|
|
421
|
+
it('displays clean message when flag is used', async () => {
|
|
422
|
+
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
423
|
+
mockStartServer.mockResolvedValue({
|
|
424
|
+
port: 3000,
|
|
425
|
+
url: 'http://localhost:3000',
|
|
426
|
+
isEmpty: false,
|
|
427
|
+
});
|
|
428
|
+
const program = new Command();
|
|
429
|
+
program
|
|
430
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
431
|
+
.argument('[compare-with]', 'compare-with')
|
|
432
|
+
.option('--port <port>', 'port', parseInt)
|
|
433
|
+
.option('--host <host>', 'host', '')
|
|
434
|
+
.option('--no-open', 'no-open')
|
|
435
|
+
.option('--mode <mode>', 'mode', 'side-by-side')
|
|
436
|
+
.option('--tui', 'tui')
|
|
437
|
+
.option('--pr <url>', 'pr')
|
|
438
|
+
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
439
|
+
.action(async (commitish, _compareWith, options) => {
|
|
440
|
+
const { url } = await startServer({
|
|
441
|
+
targetCommitish: commitish,
|
|
442
|
+
baseCommitish: commitish + '^',
|
|
443
|
+
preferredPort: options.port,
|
|
444
|
+
host: options.host,
|
|
445
|
+
openBrowser: options.open,
|
|
446
|
+
mode: options.mode,
|
|
447
|
+
clearComments: options.clean,
|
|
448
|
+
});
|
|
449
|
+
console.log(`\n🚀 difit server started on ${url}`);
|
|
450
|
+
console.log(`📋 Reviewing: ${commitish}`);
|
|
451
|
+
if (options.clean) {
|
|
452
|
+
console.log('🧹 Starting with a clean slate - all existing comments will be cleared');
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
await program.parseAsync(['--clean'], { from: 'user' });
|
|
456
|
+
expect(mockStartServer).toHaveBeenCalledWith(expect.objectContaining({
|
|
457
|
+
clearComments: true,
|
|
458
|
+
}));
|
|
459
|
+
expect(console.log).toHaveBeenCalledWith('🧹 Starting with a clean slate - all existing comments will be cleared');
|
|
460
|
+
});
|
|
461
|
+
it('does not display clean message when flag is not used', async () => {
|
|
462
|
+
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
463
|
+
mockStartServer.mockResolvedValue({
|
|
464
|
+
port: 3000,
|
|
465
|
+
url: 'http://localhost:3000',
|
|
466
|
+
isEmpty: false,
|
|
467
|
+
});
|
|
468
|
+
const program = new Command();
|
|
469
|
+
program
|
|
470
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
471
|
+
.argument('[compare-with]', 'compare-with')
|
|
472
|
+
.option('--port <port>', 'port', parseInt)
|
|
473
|
+
.option('--host <host>', 'host', '')
|
|
474
|
+
.option('--no-open', 'no-open')
|
|
475
|
+
.option('--mode <mode>', 'mode', 'side-by-side')
|
|
476
|
+
.option('--tui', 'tui')
|
|
477
|
+
.option('--pr <url>', 'pr')
|
|
478
|
+
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
479
|
+
.action(async (commitish, _compareWith, options) => {
|
|
480
|
+
const { url } = await startServer({
|
|
481
|
+
targetCommitish: commitish,
|
|
482
|
+
baseCommitish: commitish + '^',
|
|
483
|
+
preferredPort: options.port,
|
|
484
|
+
host: options.host,
|
|
485
|
+
openBrowser: options.open,
|
|
486
|
+
mode: options.mode,
|
|
487
|
+
clearComments: options.clean,
|
|
488
|
+
});
|
|
489
|
+
console.log(`\n🚀 difit server started on ${url}`);
|
|
490
|
+
console.log(`📋 Reviewing: ${commitish}`);
|
|
491
|
+
if (options.clean) {
|
|
492
|
+
console.log('🧹 Starting with a clean slate - all existing comments will be cleared');
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
await program.parseAsync([], { from: 'user' });
|
|
496
|
+
expect(mockStartServer).toHaveBeenCalledWith(expect.objectContaining({
|
|
497
|
+
clearComments: undefined,
|
|
498
|
+
}));
|
|
499
|
+
expect(console.log).not.toHaveBeenCalledWith('🧹 Starting with a clean slate - all existing comments will be cleared');
|
|
500
|
+
});
|
|
359
501
|
});
|
|
360
502
|
describe('Console output', () => {
|
|
361
503
|
it('displays server startup message with correct URL', async () => {
|
package/dist/cli/utils.d.ts
CHANGED
package/dist/cli/utils.js
CHANGED
|
@@ -72,9 +72,8 @@ export function createCommitRangeString(baseHash, targetHash) {
|
|
|
72
72
|
export function parseGitHubPrUrl(url) {
|
|
73
73
|
try {
|
|
74
74
|
const urlObj = new URL(url);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
75
|
+
// Allow any hostname for GitHub Enterprise support
|
|
76
|
+
// Just validate the path structure
|
|
78
77
|
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
|
79
78
|
if (pathParts.length < 4 || pathParts[2] !== 'pull') {
|
|
80
79
|
return null;
|
|
@@ -85,7 +84,7 @@ export function parseGitHubPrUrl(url) {
|
|
|
85
84
|
if (isNaN(pullNumber)) {
|
|
86
85
|
return null;
|
|
87
86
|
}
|
|
88
|
-
return { owner, repo, pullNumber };
|
|
87
|
+
return { owner, repo, pullNumber, hostname: urlObj.hostname };
|
|
89
88
|
}
|
|
90
89
|
catch {
|
|
91
90
|
return null;
|
|
@@ -108,9 +107,14 @@ function getGitHubToken() {
|
|
|
108
107
|
}
|
|
109
108
|
export async function fetchPrDetails(prInfo) {
|
|
110
109
|
const token = getGitHubToken();
|
|
111
|
-
const
|
|
110
|
+
const octokitOptions = {
|
|
112
111
|
auth: token,
|
|
113
|
-
}
|
|
112
|
+
};
|
|
113
|
+
// For GitHub Enterprise, set the base URL
|
|
114
|
+
if (prInfo.hostname !== 'github.com') {
|
|
115
|
+
octokitOptions.baseUrl = `https://${prInfo.hostname}/api/v3`;
|
|
116
|
+
}
|
|
117
|
+
const octokit = new Octokit(octokitOptions);
|
|
114
118
|
try {
|
|
115
119
|
const { data: pr } = await octokit.rest.pulls.get({
|
|
116
120
|
owner: prInfo.owner,
|
|
@@ -126,7 +130,22 @@ export async function fetchPrDetails(prInfo) {
|
|
|
126
130
|
}
|
|
127
131
|
catch (error) {
|
|
128
132
|
if (error instanceof Error) {
|
|
129
|
-
|
|
133
|
+
let authHint = '';
|
|
134
|
+
// Provide more specific error messages for authentication issues
|
|
135
|
+
if (error.message.includes('Bad credentials')) {
|
|
136
|
+
if (prInfo.hostname !== 'github.com') {
|
|
137
|
+
authHint = `\n\nFor GitHub Enterprise Server (${prInfo.hostname}):
|
|
138
|
+
1. Generate a token on YOUR Enterprise Server: https://${prInfo.hostname}/settings/tokens
|
|
139
|
+
2. Set it as GITHUB_TOKEN environment variable
|
|
140
|
+
3. Tokens from github.com will NOT work on Enterprise servers`;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
authHint = '\n\nTry: gh auth login or set GITHUB_TOKEN environment variable';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (!token) {
|
|
147
|
+
authHint = ' (Try: gh auth login or set GITHUB_TOKEN environment variable)';
|
|
148
|
+
}
|
|
130
149
|
throw new Error(`Failed to fetch PR details: ${error.message}${authHint}`);
|
|
131
150
|
}
|
|
132
151
|
throw new Error('Failed to fetch PR details: Unknown error');
|
|
@@ -165,7 +184,7 @@ export function resolveCommitInLocalRepo(sha, context) {
|
|
|
165
184
|
export async function resolvePrCommits(prUrl) {
|
|
166
185
|
const prInfo = parseGitHubPrUrl(prUrl);
|
|
167
186
|
if (!prInfo) {
|
|
168
|
-
throw new Error('Invalid GitHub PR URL format. Expected: https://github.com/owner/repo/pull/123');
|
|
187
|
+
throw new Error('Invalid GitHub PR URL format. Expected: https://github.com/owner/repo/pull/123 or https://github.enterprise.com/owner/repo/pull/123');
|
|
169
188
|
}
|
|
170
189
|
const prDetails = await fetchPrDetails(prInfo);
|
|
171
190
|
const context = { owner: prInfo.owner, repo: prInfo.repo };
|
package/dist/cli/utils.test.js
CHANGED
|
@@ -204,6 +204,7 @@ describe('CLI Utils', () => {
|
|
|
204
204
|
owner: 'owner',
|
|
205
205
|
repo: 'repo',
|
|
206
206
|
pullNumber: 123,
|
|
207
|
+
hostname: 'github.com',
|
|
207
208
|
});
|
|
208
209
|
});
|
|
209
210
|
it('should parse GitHub PR URLs with additional path segments', () => {
|
|
@@ -212,6 +213,7 @@ describe('CLI Utils', () => {
|
|
|
212
213
|
owner: 'owner',
|
|
213
214
|
repo: 'repo',
|
|
214
215
|
pullNumber: 456,
|
|
216
|
+
hostname: 'github.com',
|
|
215
217
|
});
|
|
216
218
|
});
|
|
217
219
|
it('should parse GitHub PR URLs with query parameters', () => {
|
|
@@ -220,6 +222,7 @@ describe('CLI Utils', () => {
|
|
|
220
222
|
owner: 'owner',
|
|
221
223
|
repo: 'repo',
|
|
222
224
|
pullNumber: 789,
|
|
225
|
+
hostname: 'github.com',
|
|
223
226
|
});
|
|
224
227
|
});
|
|
225
228
|
it('should handle URLs with hyphens and underscores in owner/repo names', () => {
|
|
@@ -228,11 +231,27 @@ describe('CLI Utils', () => {
|
|
|
228
231
|
owner: 'owner-name',
|
|
229
232
|
repo: 'repo_name',
|
|
230
233
|
pullNumber: 123,
|
|
234
|
+
hostname: 'github.com',
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
it('should parse GitHub Enterprise PR URLs', () => {
|
|
238
|
+
const result1 = parseGitHubPrUrl('https://github.enterprise.com/owner/repo/pull/123');
|
|
239
|
+
expect(result1).toEqual({
|
|
240
|
+
owner: 'owner',
|
|
241
|
+
repo: 'repo',
|
|
242
|
+
pullNumber: 123,
|
|
243
|
+
hostname: 'github.enterprise.com',
|
|
244
|
+
});
|
|
245
|
+
const result2 = parseGitHubPrUrl('https://git.company.io/team/project/pull/456');
|
|
246
|
+
expect(result2).toEqual({
|
|
247
|
+
owner: 'team',
|
|
248
|
+
repo: 'project',
|
|
249
|
+
pullNumber: 456,
|
|
250
|
+
hostname: 'git.company.io',
|
|
231
251
|
});
|
|
232
252
|
});
|
|
233
253
|
it('should return null for invalid URLs', () => {
|
|
234
254
|
expect(parseGitHubPrUrl('not-a-url')).toBe(null);
|
|
235
|
-
expect(parseGitHubPrUrl('https://example.com/owner/repo/pull/123')).toBe(null);
|
|
236
255
|
expect(parseGitHubPrUrl('https://github.com/owner/repo/issues/123')).toBe(null);
|
|
237
256
|
expect(parseGitHubPrUrl('https://github.com/owner/repo')).toBe(null);
|
|
238
257
|
expect(parseGitHubPrUrl('https://github.com/owner/repo/pull/abc')).toBe(null);
|
|
@@ -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-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);--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)}.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}.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,)}.cursor-col-resize{cursor:col-resize}.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-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-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-blue-600{background-color:var(--color-blue-600)}.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-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)}.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)}.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\: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\:text-github-text-primary:hover{color:var(--color-github-text-primary)}.hover\:opacity-80:hover{opacity:.8}}.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}@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}
|