difit 3.1.18 → 4.0.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 +30 -3
- package/README.ko.md +30 -3
- package/README.md +30 -3
- package/README.zh.md +30 -3
- package/dist/cli/github.d.ts +65 -0
- package/dist/cli/github.js +296 -0
- package/dist/cli/github.test.d.ts +1 -0
- package/dist/cli/github.test.js +341 -0
- package/dist/cli/index.js +26 -1
- package/dist/cli/index.test.js +206 -4
- package/dist/cli/utils.d.ts +2 -8
- package/dist/cli/utils.js +4 -43
- package/dist/cli/utils.test.js +50 -67
- package/dist/client/assets/{_basePickBy-DyiQWUmK.js → _basePickBy-B9N-f0iT.js} +1 -1
- package/dist/client/assets/{_baseUniq-DivSZEOF.js → _baseUniq-tbL7nVvN.js} +1 -1
- package/dist/client/assets/{arc-c0kacVOL.js → arc-BOY-7mep.js} +1 -1
- package/dist/client/assets/{architectureDiagram-2XIMDMQ5-ubymLNEe.js → architectureDiagram-2XIMDMQ5-59AvHaSB.js} +1 -1
- package/dist/client/assets/{blockDiagram-WCTKOSBZ-F9D8w4_S.js → blockDiagram-WCTKOSBZ-DXIlumQk.js} +1 -1
- package/dist/client/assets/{c4Diagram-IC4MRINW-JE9Kx4yQ.js → c4Diagram-IC4MRINW-BbfZ0uRn.js} +1 -1
- package/dist/client/assets/channel-cZXsTJxA.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CYOCoDMc.js → chunk-4BX2VUAB-l7rcB2IW.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-PRBuiJg9.js → chunk-55IACEB6-CrZL3qv9.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-C0eJ7JsI.js → chunk-FMBD7UC4-CrKv7ndg.js} +1 -1
- package/dist/client/assets/{chunk-JSJVCQXG-QZotPSqo.js → chunk-JSJVCQXG-DyBDhAEM.js} +1 -1
- package/dist/client/assets/{chunk-KX2RTZJC-B8du3tt8.js → chunk-KX2RTZJC-By5mkZmU.js} +1 -1
- package/dist/client/assets/{chunk-NQ4KR5QH-B10ldi5m.js → chunk-NQ4KR5QH-C30p9xRx.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CpwW9rUQ.js → chunk-QZHKN3VN-DVlhR2wU.js} +1 -1
- package/dist/client/assets/{chunk-WL4C6EOR-DwKPHpbL.js → chunk-WL4C6EOR-Cn7a6CO3.js} +1 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-B_coIPEy.js +1 -0
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-B_coIPEy.js +1 -0
- package/dist/client/assets/clone-BjaT2HOk.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-p76yal75.js → cose-bilkent-S5V4N54A-LyauIk_9.js} +1 -1
- package/dist/client/assets/{dagre-KLK3FWXG-CdDyed3V.js → dagre-KLK3FWXG-DRWb2KE3.js} +1 -1
- package/dist/client/assets/{diagram-E7M64L7V-BaC8dXuW.js → diagram-E7M64L7V-ChT6mNWK.js} +1 -1
- package/dist/client/assets/{diagram-IFDJBPK2-BGf8xwJI.js → diagram-IFDJBPK2-CqbTduoP.js} +1 -1
- package/dist/client/assets/{diagram-P4PSJMXO-D3j16gBZ.js → diagram-P4PSJMXO-Bzv5Z3ri.js} +1 -1
- package/dist/client/assets/{erDiagram-INFDFZHY-DFpDdocf.js → erDiagram-INFDFZHY-CvXfUZ4L.js} +1 -1
- package/dist/client/assets/{flowDiagram-PKNHOUZH-Cz4mb4IF.js → flowDiagram-PKNHOUZH-CxmpNUKq.js} +1 -1
- package/dist/client/assets/{ganttDiagram-A5KZAMGK-CNzY9ua5.js → ganttDiagram-A5KZAMGK-9LpZCsg6.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-DCSxL8EQ.js → gitGraphDiagram-K3NZZRJ6-C6yZOrQJ.js} +1 -1
- package/dist/client/assets/{graph-BC2BV1-T.js → graph-bUZ7uHLW.js} +1 -1
- package/dist/client/assets/index-BLNN1bfE.js +98 -0
- package/dist/client/assets/index-VxkpzDXr.css +1 -0
- package/dist/client/assets/{infoDiagram-LFFYTUFH-BKSspZbH.js → infoDiagram-LFFYTUFH-Djdy3W21.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-PHBUUO56-DZ2IRYwc.js → ishikawaDiagram-PHBUUO56-oOdwCpeS.js} +1 -1
- package/dist/client/assets/{journeyDiagram-4ABVD52K-BrjXAkii.js → journeyDiagram-4ABVD52K-DTb_nGAw.js} +1 -1
- package/dist/client/assets/{kanban-definition-K7BYSVSG-B1mfOekw.js → kanban-definition-K7BYSVSG-CMtP7pHA.js} +1 -1
- package/dist/client/assets/{layout-CWTG02uT.js → layout-CXr5MatK.js} +1 -1
- package/dist/client/assets/{linear-CGgOKp1d.js → linear-pOMS9pjV.js} +1 -1
- package/dist/client/assets/{mermaid.core-DTPtVBG7.js → mermaid.core-DV5JJ1Ie.js} +4 -4
- package/dist/client/assets/{mindmap-definition-YRQLILUH-DByVRPFT.js → mindmap-definition-YRQLILUH-DN-sbonc.js} +1 -1
- package/dist/client/assets/{pieDiagram-SKSYHLDU-DEgvAxAy.js → pieDiagram-SKSYHLDU-tAHCkgh1.js} +1 -1
- package/dist/client/assets/{prism-csharp-DqTrHqwJ.js → prism-csharp-5CQ0RcEE.js} +1 -1
- package/dist/client/assets/{prism-elixir-DEJaM00V.js → prism-elixir-BSOTyVg2.js} +1 -1
- package/dist/client/assets/{prism-hcl-HvJ0aPiH.js → prism-hcl-BYvi1mtM.js} +1 -1
- package/dist/client/assets/{prism-java-DDUFERTh.js → prism-java-DMU2FM4X.js} +1 -1
- package/dist/client/assets/{prism-perl-CNA3SNC9.js → prism-perl-CpfvaEQk.js} +1 -1
- package/dist/client/assets/{prism-php-hBQuhE2A.js → prism-php-SC920LoD.js} +1 -1
- package/dist/client/assets/{prism-ruby-BKap8imy.js → prism-ruby-DZph-YiO.js} +1 -1
- package/dist/client/assets/{prism-solidity-DHc7LZHq.js → prism-solidity-qTLbmiAT.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-DTtikTvc.js → quadrantDiagram-337W2JSQ-B0wODmgR.js} +1 -1
- package/dist/client/assets/{requirementDiagram-Z7DCOOCP-B34R-xD0.js → requirementDiagram-Z7DCOOCP-A3aeHC06.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-Dts1ZXRC.js → sankeyDiagram-WA2Y5GQK-BWa6kZhG.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-2WXFIKYE-DzM3WhEY.js → sequenceDiagram-2WXFIKYE-Cx_COX9G.js} +1 -1
- package/dist/client/assets/{stateDiagram-RAJIS63D-B2dF8YnK.js → stateDiagram-RAJIS63D-BXGnN6rZ.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-CMw3xNha.js +1 -0
- package/dist/client/assets/{timeline-definition-YZTLITO2-BO4OtcEm.js → timeline-definition-YZTLITO2-DbqaUm9k.js} +1 -1
- package/dist/client/assets/{treemap-KZPCXAKY-DaXnvVRH.js → treemap-KZPCXAKY-CfEujPCR.js} +1 -1
- package/dist/client/assets/{vennDiagram-LZ73GAT5-AIMhd8Js.js → vennDiagram-LZ73GAT5-CqJE8CAD.js} +1 -1
- package/dist/client/assets/{xychartDiagram-JWTSCODW-Ch6W1f7P.js → xychartDiagram-JWTSCODW-CfdDvzHC.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/generated-file-check.js +113 -58
- package/dist/server/generated-file-check.test.js +2 -0
- package/dist/server/git-diff.d.ts +1 -0
- package/dist/server/git-diff.js +33 -6
- package/dist/server/git-diff.test.js +26 -0
- package/dist/server/server.d.ts +2 -0
- package/dist/server/server.js +107 -34
- package/dist/server/server.test.js +120 -0
- package/dist/types/diff.d.ts +73 -14
- package/dist/utils/commentFormatting.d.ts +4 -2
- package/dist/utils/commentFormatting.js +57 -19
- package/dist/utils/commentImports.d.ts +9 -0
- package/dist/utils/commentImports.js +264 -0
- package/dist/utils/commentImports.test.d.ts +1 -0
- package/dist/utils/commentImports.test.js +197 -0
- package/package.json +1 -1
- package/dist/client/assets/channel-Ca4c0q8d.js +0 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-CJLw9sK7.js +0 -1
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-CJLw9sK7.js +0 -1
- package/dist/client/assets/clone-D0mDLEir.js +0 -1
- package/dist/client/assets/index-DHt9OwVU.css +0 -1
- package/dist/client/assets/index-mE8CA51x.js +0 -95
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-ReD0hBzH.js +0 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { parseGitHubPrUrl, parsePrCommentImportsResponse } from './github';
|
|
3
|
+
function createReviewComment(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
id: 'COMMENT_1',
|
|
6
|
+
body: 'Imported comment',
|
|
7
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
8
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
9
|
+
author: { login: 'octocat' },
|
|
10
|
+
...overrides,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function createReviewThread(overrides = {}) {
|
|
14
|
+
return {
|
|
15
|
+
id: 'THREAD_1',
|
|
16
|
+
isResolved: false,
|
|
17
|
+
isOutdated: false,
|
|
18
|
+
subjectType: 'LINE',
|
|
19
|
+
path: 'src/example.ts',
|
|
20
|
+
diffSide: 'RIGHT',
|
|
21
|
+
startDiffSide: null,
|
|
22
|
+
line: 12,
|
|
23
|
+
startLine: null,
|
|
24
|
+
originalLine: 11,
|
|
25
|
+
originalStartLine: null,
|
|
26
|
+
comments: {
|
|
27
|
+
nodes: [createReviewComment()],
|
|
28
|
+
},
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vi.restoreAllMocks();
|
|
34
|
+
});
|
|
35
|
+
describe('CLI GitHub utils', () => {
|
|
36
|
+
describe('parsePrCommentImportsResponse', () => {
|
|
37
|
+
it('imports unresolved inline threads, sorts comments, and skips non-importable threads', () => {
|
|
38
|
+
const response = {
|
|
39
|
+
data: {
|
|
40
|
+
repository: {
|
|
41
|
+
pullRequest: {
|
|
42
|
+
reviewThreads: {
|
|
43
|
+
nodes: [
|
|
44
|
+
createReviewThread({
|
|
45
|
+
id: 'THREAD_UNRESOLVED',
|
|
46
|
+
diffSide: 'RIGHT',
|
|
47
|
+
line: 14,
|
|
48
|
+
comments: {
|
|
49
|
+
nodes: [
|
|
50
|
+
createReviewComment({
|
|
51
|
+
id: 'COMMENT_REPLY',
|
|
52
|
+
body: 'Second comment',
|
|
53
|
+
createdAt: '2026-03-25T09:10:00Z',
|
|
54
|
+
updatedAt: '2026-03-25T09:12:00Z',
|
|
55
|
+
author: { login: 'reviewer-2' },
|
|
56
|
+
}),
|
|
57
|
+
createReviewComment({
|
|
58
|
+
id: 'COMMENT_ROOT',
|
|
59
|
+
body: 'First comment',
|
|
60
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
61
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
62
|
+
author: { login: 'reviewer-1' },
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
createReviewThread({
|
|
68
|
+
id: 'THREAD_RESOLVED',
|
|
69
|
+
isResolved: true,
|
|
70
|
+
}),
|
|
71
|
+
createReviewThread({
|
|
72
|
+
id: 'THREAD_OUTDATED',
|
|
73
|
+
isOutdated: true,
|
|
74
|
+
}),
|
|
75
|
+
createReviewThread({
|
|
76
|
+
id: 'THREAD_FILE',
|
|
77
|
+
subjectType: 'FILE',
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
pageInfo: {
|
|
81
|
+
hasNextPage: true,
|
|
82
|
+
endCursor: 'CURSOR_1',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const result = parsePrCommentImportsResponse(response);
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
commentImports: [
|
|
92
|
+
{
|
|
93
|
+
type: 'thread',
|
|
94
|
+
id: 'COMMENT_ROOT',
|
|
95
|
+
filePath: 'src/example.ts',
|
|
96
|
+
position: { side: 'new', line: 14 },
|
|
97
|
+
body: 'First comment',
|
|
98
|
+
author: 'reviewer-1',
|
|
99
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
100
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: 'reply',
|
|
104
|
+
id: 'COMMENT_REPLY',
|
|
105
|
+
filePath: 'src/example.ts',
|
|
106
|
+
position: { side: 'new', line: 14 },
|
|
107
|
+
body: 'Second comment',
|
|
108
|
+
author: 'reviewer-2',
|
|
109
|
+
createdAt: '2026-03-25T09:10:00Z',
|
|
110
|
+
updatedAt: '2026-03-25T09:12:00Z',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
pageInfo: {
|
|
114
|
+
hasNextPage: true,
|
|
115
|
+
endCursor: 'CURSOR_1',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
it('maps RIGHT and LEFT threads to new and old diff positions for single and multi-line comments', () => {
|
|
120
|
+
const response = {
|
|
121
|
+
data: {
|
|
122
|
+
repository: {
|
|
123
|
+
pullRequest: {
|
|
124
|
+
reviewThreads: {
|
|
125
|
+
nodes: [
|
|
126
|
+
createReviewThread({
|
|
127
|
+
id: 'THREAD_RIGHT_SINGLE',
|
|
128
|
+
diffSide: 'RIGHT',
|
|
129
|
+
line: 20,
|
|
130
|
+
startLine: 20,
|
|
131
|
+
startDiffSide: null,
|
|
132
|
+
comments: {
|
|
133
|
+
nodes: [createReviewComment({ id: 'COMMENT_RIGHT_SINGLE' })],
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
136
|
+
createReviewThread({
|
|
137
|
+
id: 'THREAD_RIGHT_MULTI',
|
|
138
|
+
diffSide: 'RIGHT',
|
|
139
|
+
line: 24,
|
|
140
|
+
startLine: 21,
|
|
141
|
+
startDiffSide: 'RIGHT',
|
|
142
|
+
comments: {
|
|
143
|
+
nodes: [createReviewComment({ id: 'COMMENT_RIGHT_MULTI' })],
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
createReviewThread({
|
|
147
|
+
id: 'THREAD_LEFT_SINGLE',
|
|
148
|
+
diffSide: 'LEFT',
|
|
149
|
+
line: null,
|
|
150
|
+
originalLine: 8,
|
|
151
|
+
originalStartLine: null,
|
|
152
|
+
startDiffSide: null,
|
|
153
|
+
comments: {
|
|
154
|
+
nodes: [createReviewComment({ id: 'COMMENT_LEFT_SINGLE' })],
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
createReviewThread({
|
|
158
|
+
id: 'THREAD_LEFT_MULTI',
|
|
159
|
+
diffSide: 'LEFT',
|
|
160
|
+
line: null,
|
|
161
|
+
originalLine: 11,
|
|
162
|
+
originalStartLine: 7,
|
|
163
|
+
startDiffSide: 'LEFT',
|
|
164
|
+
comments: {
|
|
165
|
+
nodes: [createReviewComment({ id: 'COMMENT_LEFT_MULTI' })],
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
],
|
|
169
|
+
pageInfo: {
|
|
170
|
+
hasNextPage: false,
|
|
171
|
+
endCursor: null,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const result = parsePrCommentImportsResponse(response);
|
|
179
|
+
expect(result.commentImports).toEqual([
|
|
180
|
+
{
|
|
181
|
+
type: 'thread',
|
|
182
|
+
id: 'COMMENT_RIGHT_SINGLE',
|
|
183
|
+
filePath: 'src/example.ts',
|
|
184
|
+
position: { side: 'new', line: 20 },
|
|
185
|
+
body: 'Imported comment',
|
|
186
|
+
author: 'octocat',
|
|
187
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
188
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: 'thread',
|
|
192
|
+
id: 'COMMENT_RIGHT_MULTI',
|
|
193
|
+
filePath: 'src/example.ts',
|
|
194
|
+
position: { side: 'new', line: { start: 21, end: 24 } },
|
|
195
|
+
body: 'Imported comment',
|
|
196
|
+
author: 'octocat',
|
|
197
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
198
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: 'thread',
|
|
202
|
+
id: 'COMMENT_LEFT_SINGLE',
|
|
203
|
+
filePath: 'src/example.ts',
|
|
204
|
+
position: { side: 'old', line: 8 },
|
|
205
|
+
body: 'Imported comment',
|
|
206
|
+
author: 'octocat',
|
|
207
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
208
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: 'thread',
|
|
212
|
+
id: 'COMMENT_LEFT_MULTI',
|
|
213
|
+
filePath: 'src/example.ts',
|
|
214
|
+
position: { side: 'old', line: { start: 7, end: 11 } },
|
|
215
|
+
body: 'Imported comment',
|
|
216
|
+
author: 'octocat',
|
|
217
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
218
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
});
|
|
222
|
+
it('warns and skips threads with invalid line mapping', () => {
|
|
223
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
224
|
+
const response = {
|
|
225
|
+
data: {
|
|
226
|
+
repository: {
|
|
227
|
+
pullRequest: {
|
|
228
|
+
reviewThreads: {
|
|
229
|
+
nodes: [
|
|
230
|
+
createReviewThread({
|
|
231
|
+
id: 'THREAD_RIGHT_MISSING_LINE',
|
|
232
|
+
diffSide: 'RIGHT',
|
|
233
|
+
line: null,
|
|
234
|
+
}),
|
|
235
|
+
createReviewThread({
|
|
236
|
+
id: 'THREAD_LEFT_MISSING_ORIGINAL',
|
|
237
|
+
diffSide: 'LEFT',
|
|
238
|
+
line: null,
|
|
239
|
+
originalLine: null,
|
|
240
|
+
}),
|
|
241
|
+
createReviewThread({
|
|
242
|
+
id: 'THREAD_RIGHT_BAD_RANGE',
|
|
243
|
+
diffSide: 'RIGHT',
|
|
244
|
+
line: 9,
|
|
245
|
+
startLine: 12,
|
|
246
|
+
startDiffSide: 'RIGHT',
|
|
247
|
+
}),
|
|
248
|
+
createReviewThread({
|
|
249
|
+
id: 'THREAD_LEFT_BAD_SIDE',
|
|
250
|
+
diffSide: 'LEFT',
|
|
251
|
+
line: null,
|
|
252
|
+
originalLine: 9,
|
|
253
|
+
originalStartLine: 7,
|
|
254
|
+
startDiffSide: 'RIGHT',
|
|
255
|
+
}),
|
|
256
|
+
],
|
|
257
|
+
pageInfo: {
|
|
258
|
+
hasNextPage: false,
|
|
259
|
+
endCursor: null,
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
const result = parsePrCommentImportsResponse(response);
|
|
267
|
+
expect(result.commentImports).toEqual([]);
|
|
268
|
+
expect(warnSpy).toHaveBeenCalledTimes(4);
|
|
269
|
+
expect(warnSpy).toHaveBeenNthCalledWith(1, 'Warning: Skipping PR review thread THREAD_RIGHT_MISSING_LINE: RIGHT thread is missing line.');
|
|
270
|
+
expect(warnSpy).toHaveBeenNthCalledWith(2, 'Warning: Skipping PR review thread THREAD_LEFT_MISSING_ORIGINAL: LEFT thread is missing originalLine.');
|
|
271
|
+
expect(warnSpy).toHaveBeenNthCalledWith(3, 'Warning: Skipping PR review thread THREAD_RIGHT_BAD_RANGE: RIGHT thread has an invalid multi-line range.');
|
|
272
|
+
expect(warnSpy).toHaveBeenNthCalledWith(4, 'Warning: Skipping PR review thread THREAD_LEFT_BAD_SIDE: LEFT thread has mismatched startDiffSide.');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('parseGitHubPrUrl', () => {
|
|
276
|
+
it('should parse valid GitHub PR URLs', () => {
|
|
277
|
+
const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/123');
|
|
278
|
+
expect(result).toEqual({
|
|
279
|
+
owner: 'owner',
|
|
280
|
+
repo: 'repo',
|
|
281
|
+
pullNumber: 123,
|
|
282
|
+
hostname: 'github.com',
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
it('should parse GitHub PR URLs with additional path segments', () => {
|
|
286
|
+
const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/456/files');
|
|
287
|
+
expect(result).toEqual({
|
|
288
|
+
owner: 'owner',
|
|
289
|
+
repo: 'repo',
|
|
290
|
+
pullNumber: 456,
|
|
291
|
+
hostname: 'github.com',
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
it('should parse GitHub PR URLs with query parameters', () => {
|
|
295
|
+
const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/789?tab=files');
|
|
296
|
+
expect(result).toEqual({
|
|
297
|
+
owner: 'owner',
|
|
298
|
+
repo: 'repo',
|
|
299
|
+
pullNumber: 789,
|
|
300
|
+
hostname: 'github.com',
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
it('should handle URLs with hyphens and underscores in owner/repo names', () => {
|
|
304
|
+
const result = parseGitHubPrUrl('https://github.com/owner-name/repo_name/pull/123');
|
|
305
|
+
expect(result).toEqual({
|
|
306
|
+
owner: 'owner-name',
|
|
307
|
+
repo: 'repo_name',
|
|
308
|
+
pullNumber: 123,
|
|
309
|
+
hostname: 'github.com',
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
it('should parse GitHub Enterprise PR URLs', () => {
|
|
313
|
+
const result1 = parseGitHubPrUrl('https://github.enterprise.com/owner/repo/pull/123');
|
|
314
|
+
expect(result1).toEqual({
|
|
315
|
+
owner: 'owner',
|
|
316
|
+
repo: 'repo',
|
|
317
|
+
pullNumber: 123,
|
|
318
|
+
hostname: 'github.enterprise.com',
|
|
319
|
+
});
|
|
320
|
+
const result2 = parseGitHubPrUrl('https://git.company.io/team/project/pull/456');
|
|
321
|
+
expect(result2).toEqual({
|
|
322
|
+
owner: 'team',
|
|
323
|
+
repo: 'project',
|
|
324
|
+
pullNumber: 456,
|
|
325
|
+
hostname: 'git.company.io',
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
it('should return null for invalid URLs', () => {
|
|
329
|
+
expect(parseGitHubPrUrl('not-a-url')).toBe(null);
|
|
330
|
+
expect(parseGitHubPrUrl('https://github.com/owner/repo/issues/123')).toBe(null);
|
|
331
|
+
expect(parseGitHubPrUrl('https://github.com/owner/repo')).toBe(null);
|
|
332
|
+
expect(parseGitHubPrUrl('https://github.com/owner/repo/pull/abc')).toBe(null);
|
|
333
|
+
});
|
|
334
|
+
it('should handle malformed URLs gracefully', () => {
|
|
335
|
+
expect(parseGitHubPrUrl('')).toBe(null);
|
|
336
|
+
expect(parseGitHubPrUrl('https://github.com')).toBe(null);
|
|
337
|
+
expect(parseGitHubPrUrl('https://github.com/owner')).toBe(null);
|
|
338
|
+
expect(parseGitHubPrUrl('https://github.com/owner/repo/pull')).toBe(null);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|
package/dist/cli/index.js
CHANGED
|
@@ -6,7 +6,8 @@ import pkg from '../../package.json' with { type: 'json' };
|
|
|
6
6
|
import { startServer } from '../server/server.js';
|
|
7
7
|
import { DiffMode } from '../types/watch.js';
|
|
8
8
|
import { DEFAULT_DIFF_VIEW_MODE, normalizeDiffViewMode } from '../utils/diffMode.js';
|
|
9
|
-
import { shouldReadStdin, findUntrackedFiles, markFilesIntentToAdd, promptUser,
|
|
9
|
+
import { shouldReadStdin, findUntrackedFiles, markFilesIntentToAdd, promptUser, parseCommentOptions, validateDiffArguments, getGitRoot, } from './utils.js';
|
|
10
|
+
import { getPrPatch, getPrCommentImports } from './github.js';
|
|
10
11
|
function isSpecialArg(arg) {
|
|
11
12
|
return arg === 'working' || arg === 'staged' || arg === '.';
|
|
12
13
|
}
|
|
@@ -39,6 +40,7 @@ program
|
|
|
39
40
|
.option('--host <host>', 'host address to bind', '')
|
|
40
41
|
.option('--no-open', 'do not automatically open browser')
|
|
41
42
|
.option('--mode <mode>', 'diff mode (split or unified)', normalizeDiffViewMode, DEFAULT_DIFF_VIEW_MODE)
|
|
43
|
+
.option('--comment <json>', 'inject initial review comments (repeatable, accepts a JSON object or array)', (value, previous = []) => [...previous, value], [])
|
|
42
44
|
.option('--tui', 'use terminal UI instead of web interface')
|
|
43
45
|
.option('--pr <url>', 'GitHub PR URL to review (e.g., https://github.com/owner/repo/pull/123)')
|
|
44
46
|
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
@@ -48,6 +50,16 @@ program
|
|
|
48
50
|
try {
|
|
49
51
|
let stdinDiff;
|
|
50
52
|
let stdinReviewLabel = 'diff from stdin';
|
|
53
|
+
let manualCommentImports = [];
|
|
54
|
+
let commentImports = [];
|
|
55
|
+
try {
|
|
56
|
+
manualCommentImports = parseCommentOptions(options.comment);
|
|
57
|
+
commentImports = manualCommentImports;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Invalid --comment value'}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
51
63
|
if (options.pr) {
|
|
52
64
|
if (commitish !== 'HEAD' || compareWith) {
|
|
53
65
|
console.error('Error: --pr option cannot be used with positional arguments');
|
|
@@ -65,6 +77,13 @@ program
|
|
|
65
77
|
console.error(`Error resolving PR: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
66
78
|
process.exit(1);
|
|
67
79
|
}
|
|
80
|
+
try {
|
|
81
|
+
const prCommentImports = await getPrCommentImports(options.pr);
|
|
82
|
+
commentImports = [...prCommentImports, ...manualCommentImports];
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.warn(`Warning: Failed to load PR review comments: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
86
|
+
}
|
|
68
87
|
}
|
|
69
88
|
else {
|
|
70
89
|
// Check if we should read from stdin
|
|
@@ -93,6 +112,7 @@ program
|
|
|
93
112
|
mode: options.mode,
|
|
94
113
|
clearComments: options.clean,
|
|
95
114
|
keepAlive: options.keepAlive,
|
|
115
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
96
116
|
});
|
|
97
117
|
console.log(`\n🚀 difit server started on ${url}`);
|
|
98
118
|
console.log(`📋 Reviewing: ${stdinReviewLabel}`);
|
|
@@ -136,6 +156,10 @@ program
|
|
|
136
156
|
await handleUntrackedFiles(git, options.includeUntracked);
|
|
137
157
|
}
|
|
138
158
|
if (options.tui) {
|
|
159
|
+
if (commentImports.length > 0) {
|
|
160
|
+
console.error('Error: --comment option cannot be used with --tui');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
139
163
|
// Check if we're in a TTY environment
|
|
140
164
|
if (!process.stdin.isTTY) {
|
|
141
165
|
console.error('Error: TUI mode requires an interactive terminal (TTY).');
|
|
@@ -169,6 +193,7 @@ program
|
|
|
169
193
|
keepAlive: options.keepAlive,
|
|
170
194
|
diffMode: determineDiffMode(targetCommitish, compareWith),
|
|
171
195
|
repoPath,
|
|
196
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
172
197
|
});
|
|
173
198
|
console.log(`\n🚀 difit server started on ${url}`);
|
|
174
199
|
console.log(`📋 Reviewing: ${targetCommitish}`);
|