difit 3.1.18 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +44 -16
- package/README.ko.md +44 -16
- package/README.md +44 -16
- package/README.zh.md +44 -16
- 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 +42 -1
- package/dist/cli/index.test.js +330 -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-ChXFkTMC.js} +1 -1
- package/dist/client/assets/{_baseUniq-DivSZEOF.js → _baseUniq-Mj_sFFQW.js} +1 -1
- package/dist/client/assets/{arc-c0kacVOL.js → arc-BMA6S9F1.js} +1 -1
- package/dist/client/assets/{architectureDiagram-2XIMDMQ5-ubymLNEe.js → architectureDiagram-2XIMDMQ5-0uiM_v5K.js} +1 -1
- package/dist/client/assets/{blockDiagram-WCTKOSBZ-F9D8w4_S.js → blockDiagram-WCTKOSBZ-CM7ZLL6F.js} +1 -1
- package/dist/client/assets/{c4Diagram-IC4MRINW-JE9Kx4yQ.js → c4Diagram-IC4MRINW-DKtCnVwn.js} +1 -1
- package/dist/client/assets/channel-D057yzDp.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CYOCoDMc.js → chunk-4BX2VUAB-Wsl8DxEB.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-PRBuiJg9.js → chunk-55IACEB6-CHm9X5i7.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-C0eJ7JsI.js → chunk-FMBD7UC4-BSa8SHgd.js} +1 -1
- package/dist/client/assets/{chunk-JSJVCQXG-QZotPSqo.js → chunk-JSJVCQXG-Cpk76oJ3.js} +1 -1
- package/dist/client/assets/{chunk-KX2RTZJC-B8du3tt8.js → chunk-KX2RTZJC-D8YvfZVu.js} +1 -1
- package/dist/client/assets/{chunk-NQ4KR5QH-B10ldi5m.js → chunk-NQ4KR5QH-BogviJOv.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CpwW9rUQ.js → chunk-QZHKN3VN-DwLJYu26.js} +1 -1
- package/dist/client/assets/{chunk-WL4C6EOR-DwKPHpbL.js → chunk-WL4C6EOR-BFDpGxW2.js} +1 -1
- package/dist/client/assets/classDiagram-VBA2DB6C---D4iOts.js +1 -0
- package/dist/client/assets/classDiagram-v2-RAHNMMFH---D4iOts.js +1 -0
- package/dist/client/assets/clone-xSR3otEf.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-p76yal75.js → cose-bilkent-S5V4N54A-oEosZ_5y.js} +1 -1
- package/dist/client/assets/{dagre-KLK3FWXG-CdDyed3V.js → dagre-KLK3FWXG-gFld4u1H.js} +1 -1
- package/dist/client/assets/{diagram-E7M64L7V-BaC8dXuW.js → diagram-E7M64L7V-gJq3kSrf.js} +1 -1
- package/dist/client/assets/{diagram-IFDJBPK2-BGf8xwJI.js → diagram-IFDJBPK2-BsUm_q22.js} +1 -1
- package/dist/client/assets/{diagram-P4PSJMXO-D3j16gBZ.js → diagram-P4PSJMXO-juB-sfcR.js} +1 -1
- package/dist/client/assets/{erDiagram-INFDFZHY-DFpDdocf.js → erDiagram-INFDFZHY-Dn77qXAt.js} +1 -1
- package/dist/client/assets/{flowDiagram-PKNHOUZH-Cz4mb4IF.js → flowDiagram-PKNHOUZH-DtmvDYdN.js} +1 -1
- package/dist/client/assets/{ganttDiagram-A5KZAMGK-CNzY9ua5.js → ganttDiagram-A5KZAMGK-BlDaKLbQ.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-DCSxL8EQ.js → gitGraphDiagram-K3NZZRJ6-DeAAeuMS.js} +1 -1
- package/dist/client/assets/{graph-BC2BV1-T.js → graph-NX9gBP47.js} +1 -1
- package/dist/client/assets/index-VxkpzDXr.css +1 -0
- package/dist/client/assets/index-kJdw4DY-.js +98 -0
- package/dist/client/assets/{infoDiagram-LFFYTUFH-BKSspZbH.js → infoDiagram-LFFYTUFH-CAaX023c.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-PHBUUO56-DZ2IRYwc.js → ishikawaDiagram-PHBUUO56-CmiTQStv.js} +1 -1
- package/dist/client/assets/{journeyDiagram-4ABVD52K-BrjXAkii.js → journeyDiagram-4ABVD52K-B0SHC7mz.js} +1 -1
- package/dist/client/assets/{kanban-definition-K7BYSVSG-B1mfOekw.js → kanban-definition-K7BYSVSG-IfRdhzz7.js} +1 -1
- package/dist/client/assets/{layout-CWTG02uT.js → layout-l3OdNQhJ.js} +1 -1
- package/dist/client/assets/{linear-CGgOKp1d.js → linear-CQ0hx5Qs.js} +1 -1
- package/dist/client/assets/{mermaid.core-DTPtVBG7.js → mermaid.core-DqlPTabt.js} +4 -4
- package/dist/client/assets/{mindmap-definition-YRQLILUH-DByVRPFT.js → mindmap-definition-YRQLILUH-DIgSmG_f.js} +1 -1
- package/dist/client/assets/{pieDiagram-SKSYHLDU-DEgvAxAy.js → pieDiagram-SKSYHLDU-FzM5qoIB.js} +1 -1
- package/dist/client/assets/{prism-csharp-DqTrHqwJ.js → prism-csharp-DCfUUOUs.js} +1 -1
- package/dist/client/assets/{prism-elixir-DEJaM00V.js → prism-elixir-riuOL1mm.js} +1 -1
- package/dist/client/assets/{prism-hcl-HvJ0aPiH.js → prism-hcl-CizuX1s4.js} +1 -1
- package/dist/client/assets/{prism-java-DDUFERTh.js → prism-java-DYCKrDUh.js} +1 -1
- package/dist/client/assets/{prism-perl-CNA3SNC9.js → prism-perl-BJwBYR3Y.js} +1 -1
- package/dist/client/assets/{prism-php-hBQuhE2A.js → prism-php-BMhFuA7y.js} +1 -1
- package/dist/client/assets/{prism-ruby-BKap8imy.js → prism-ruby-Bcu0cDEh.js} +1 -1
- package/dist/client/assets/{prism-solidity-DHc7LZHq.js → prism-solidity-DDDs3w-w.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-DTtikTvc.js → quadrantDiagram-337W2JSQ-BBrApyD7.js} +1 -1
- package/dist/client/assets/{requirementDiagram-Z7DCOOCP-B34R-xD0.js → requirementDiagram-Z7DCOOCP-CLXiwUaA.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-Dts1ZXRC.js → sankeyDiagram-WA2Y5GQK-9Y3Ly5qe.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-2WXFIKYE-DzM3WhEY.js → sequenceDiagram-2WXFIKYE-DEpX1BA5.js} +1 -1
- package/dist/client/assets/{stateDiagram-RAJIS63D-B2dF8YnK.js → stateDiagram-RAJIS63D-Ck3ullwA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-X6UiDsar.js +1 -0
- package/dist/client/assets/{timeline-definition-YZTLITO2-BO4OtcEm.js → timeline-definition-YZTLITO2-CMezf3XV.js} +1 -1
- package/dist/client/assets/{treemap-KZPCXAKY-DaXnvVRH.js → treemap-KZPCXAKY-DqrcV0gQ.js} +1 -1
- package/dist/client/assets/{vennDiagram-LZ73GAT5-AIMhd8Js.js → vennDiagram-LZ73GAT5-eQg945Fz.js} +1 -1
- package/dist/client/assets/{xychartDiagram-JWTSCODW-Ch6W1f7P.js → xychartDiagram-JWTSCODW-_hqdXeX1.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-tui.d.ts +1 -1
- package/dist/server/git-diff-tui.js +7 -5
- package/dist/server/git-diff-tui.test.d.ts +1 -0
- package/dist/server/git-diff-tui.test.js +60 -0
- package/dist/server/git-diff.d.ts +4 -1
- package/dist/server/git-diff.js +73 -9
- package/dist/server/git-diff.test.js +46 -0
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.js +111 -37
- package/dist/server/server.test.js +152 -0
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/App.js +2 -2
- package/dist/types/diff.d.ts +74 -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
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,15 +40,32 @@ 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')
|
|
45
47
|
.option('--include-untracked', 'automatically include untracked files in diff')
|
|
46
48
|
.option('--keep-alive', 'keep server running even after browser disconnects')
|
|
49
|
+
.option('--context <lines>', 'number of context lines shown around each change', parseInt)
|
|
47
50
|
.action(async (commitish, compareWith, options) => {
|
|
48
51
|
try {
|
|
49
52
|
let stdinDiff;
|
|
50
53
|
let stdinReviewLabel = 'diff from stdin';
|
|
54
|
+
let manualCommentImports = [];
|
|
55
|
+
let commentImports = [];
|
|
56
|
+
if (options.context !== undefined &&
|
|
57
|
+
(!Number.isInteger(options.context) || options.context < 0)) {
|
|
58
|
+
console.error('Error: --context must be a non-negative integer');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
manualCommentImports = parseCommentOptions(options.comment);
|
|
63
|
+
commentImports = manualCommentImports;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Invalid --comment value'}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
51
69
|
if (options.pr) {
|
|
52
70
|
if (commitish !== 'HEAD' || compareWith) {
|
|
53
71
|
console.error('Error: --pr option cannot be used with positional arguments');
|
|
@@ -57,6 +75,10 @@ program
|
|
|
57
75
|
console.error('Error: --pr option cannot be used with --tui');
|
|
58
76
|
process.exit(1);
|
|
59
77
|
}
|
|
78
|
+
if (options.context !== undefined) {
|
|
79
|
+
console.error('Error: --context option cannot be used with --pr');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
60
82
|
try {
|
|
61
83
|
stdinDiff = getPrPatch(options.pr);
|
|
62
84
|
stdinReviewLabel = options.pr;
|
|
@@ -65,6 +87,13 @@ program
|
|
|
65
87
|
console.error(`Error resolving PR: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
66
88
|
process.exit(1);
|
|
67
89
|
}
|
|
90
|
+
try {
|
|
91
|
+
const prCommentImports = await getPrCommentImports(options.pr);
|
|
92
|
+
commentImports = [...prCommentImports, ...manualCommentImports];
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.warn(`Warning: Failed to load PR review comments: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
96
|
+
}
|
|
68
97
|
}
|
|
69
98
|
else {
|
|
70
99
|
// Check if we should read from stdin
|
|
@@ -75,6 +104,10 @@ program
|
|
|
75
104
|
hasTuiOption: Boolean(options.tui),
|
|
76
105
|
});
|
|
77
106
|
if (readFromStdin) {
|
|
107
|
+
if (options.context !== undefined) {
|
|
108
|
+
console.error('Error: --context option cannot be used with stdin diff');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
78
111
|
// Read unified diff from stdin
|
|
79
112
|
stdinDiff = await readStdin();
|
|
80
113
|
if (!stdinDiff.trim()) {
|
|
@@ -93,6 +126,7 @@ program
|
|
|
93
126
|
mode: options.mode,
|
|
94
127
|
clearComments: options.clean,
|
|
95
128
|
keepAlive: options.keepAlive,
|
|
129
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
96
130
|
});
|
|
97
131
|
console.log(`\n🚀 difit server started on ${url}`);
|
|
98
132
|
console.log(`📋 Reviewing: ${stdinReviewLabel}`);
|
|
@@ -136,6 +170,10 @@ program
|
|
|
136
170
|
await handleUntrackedFiles(git, options.includeUntracked);
|
|
137
171
|
}
|
|
138
172
|
if (options.tui) {
|
|
173
|
+
if (commentImports.length > 0) {
|
|
174
|
+
console.error('Error: --comment option cannot be used with --tui');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
139
177
|
// Check if we're in a TTY environment
|
|
140
178
|
if (!process.stdin.isTTY) {
|
|
141
179
|
console.error('Error: TUI mode requires an interactive terminal (TTY).');
|
|
@@ -150,6 +188,7 @@ program
|
|
|
150
188
|
baseCommitish,
|
|
151
189
|
mode: options.mode,
|
|
152
190
|
repoPath,
|
|
191
|
+
contextLines: options.context,
|
|
153
192
|
}));
|
|
154
193
|
return;
|
|
155
194
|
}
|
|
@@ -167,8 +206,10 @@ program
|
|
|
167
206
|
mode: options.mode,
|
|
168
207
|
clearComments: options.clean,
|
|
169
208
|
keepAlive: options.keepAlive,
|
|
209
|
+
contextLines: options.context,
|
|
170
210
|
diffMode: determineDiffMode(targetCommitish, compareWith),
|
|
171
211
|
repoPath,
|
|
212
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
172
213
|
});
|
|
173
214
|
console.log(`\n🚀 difit server started on ${url}`);
|
|
174
215
|
console.log(`📋 Reviewing: ${targetCommitish}`);
|
package/dist/cli/index.test.js
CHANGED
|
@@ -14,12 +14,16 @@ vi.mock('./utils.js', async () => {
|
|
|
14
14
|
promptUser: vi.fn(),
|
|
15
15
|
findUntrackedFiles: vi.fn(),
|
|
16
16
|
markFilesIntentToAdd: vi.fn(),
|
|
17
|
-
getPrPatch: vi.fn(),
|
|
18
17
|
};
|
|
19
18
|
});
|
|
19
|
+
vi.mock('./github.js', () => ({
|
|
20
|
+
getPrPatch: vi.fn(),
|
|
21
|
+
getPrCommentImports: vi.fn(),
|
|
22
|
+
}));
|
|
20
23
|
const { simpleGit } = await import('simple-git');
|
|
21
24
|
const { startServer } = await import('../server/server.js');
|
|
22
|
-
const { promptUser, findUntrackedFiles, markFilesIntentToAdd,
|
|
25
|
+
const { promptUser, findUntrackedFiles, markFilesIntentToAdd, parseCommentOptions, shouldReadStdin, } = await import('./utils.js');
|
|
26
|
+
const { getPrPatch, getPrCommentImports } = await import('./github.js');
|
|
23
27
|
describe('CLI index.ts', () => {
|
|
24
28
|
let mockGit;
|
|
25
29
|
let mockStartServer;
|
|
@@ -27,9 +31,12 @@ describe('CLI index.ts', () => {
|
|
|
27
31
|
let mockFindUntrackedFiles;
|
|
28
32
|
let mockMarkFilesIntentToAdd;
|
|
29
33
|
let mockGetPrPatch;
|
|
34
|
+
let mockGetPrCommentImports;
|
|
35
|
+
let actualParseCommentOptions;
|
|
30
36
|
// Store original console methods
|
|
31
37
|
let originalConsoleLog;
|
|
32
38
|
let originalConsoleError;
|
|
39
|
+
let originalConsoleWarn;
|
|
33
40
|
let originalProcessExit;
|
|
34
41
|
beforeEach(() => {
|
|
35
42
|
// Setup mocks
|
|
@@ -48,12 +55,17 @@ describe('CLI index.ts', () => {
|
|
|
48
55
|
mockFindUntrackedFiles = vi.mocked(findUntrackedFiles);
|
|
49
56
|
mockMarkFilesIntentToAdd = vi.mocked(markFilesIntentToAdd);
|
|
50
57
|
mockGetPrPatch = vi.mocked(getPrPatch);
|
|
58
|
+
mockGetPrCommentImports = vi.mocked(getPrCommentImports);
|
|
59
|
+
mockGetPrCommentImports.mockResolvedValue([]);
|
|
60
|
+
actualParseCommentOptions = parseCommentOptions;
|
|
51
61
|
// Mock console and process.exit
|
|
52
62
|
originalConsoleLog = console.log;
|
|
53
63
|
originalConsoleError = console.error;
|
|
64
|
+
originalConsoleWarn = console.warn;
|
|
54
65
|
originalProcessExit = process.exit;
|
|
55
66
|
console.log = vi.fn();
|
|
56
67
|
console.error = vi.fn();
|
|
68
|
+
console.warn = vi.fn();
|
|
57
69
|
process.exit = vi.fn();
|
|
58
70
|
// Reset all mocks
|
|
59
71
|
vi.clearAllMocks();
|
|
@@ -62,6 +74,7 @@ describe('CLI index.ts', () => {
|
|
|
62
74
|
// Restore original methods
|
|
63
75
|
console.log = originalConsoleLog;
|
|
64
76
|
console.error = originalConsoleError;
|
|
77
|
+
console.warn = originalConsoleWarn;
|
|
65
78
|
process.exit = originalProcessExit;
|
|
66
79
|
});
|
|
67
80
|
describe('CLI argument processing', () => {
|
|
@@ -200,6 +213,11 @@ describe('CLI index.ts', () => {
|
|
|
200
213
|
args: ['--keep-alive'],
|
|
201
214
|
expectedOptions: { keepAlive: true },
|
|
202
215
|
},
|
|
216
|
+
{
|
|
217
|
+
name: '--context option',
|
|
218
|
+
args: ['--context', '5'],
|
|
219
|
+
expectedOptions: { context: 5 },
|
|
220
|
+
},
|
|
203
221
|
])('$name', async ({ args, expectedOptions }) => {
|
|
204
222
|
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
205
223
|
const program = new Command();
|
|
@@ -214,6 +232,7 @@ describe('CLI index.ts', () => {
|
|
|
214
232
|
.option('--pr <url>', 'pr')
|
|
215
233
|
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
216
234
|
.option('--keep-alive', 'keep server running even after browser disconnects')
|
|
235
|
+
.option('--context <lines>', 'context', parseInt)
|
|
217
236
|
.action(async (commitish, _compareWith, options) => {
|
|
218
237
|
let targetCommitish = commitish;
|
|
219
238
|
let baseCommitish = commitish + '^';
|
|
@@ -226,6 +245,7 @@ describe('CLI index.ts', () => {
|
|
|
226
245
|
mode: options.mode,
|
|
227
246
|
clearComments: options.clean,
|
|
228
247
|
keepAlive: options.keepAlive,
|
|
248
|
+
contextLines: options.context,
|
|
229
249
|
});
|
|
230
250
|
});
|
|
231
251
|
await program.parseAsync([...args], { from: 'user' });
|
|
@@ -238,10 +258,91 @@ describe('CLI index.ts', () => {
|
|
|
238
258
|
mode: expectedOptions.mode || 'split',
|
|
239
259
|
clearComments: expectedOptions.clean,
|
|
240
260
|
keepAlive: expectedOptions.keepAlive,
|
|
261
|
+
contextLines: expectedOptions.context,
|
|
241
262
|
};
|
|
242
263
|
expect(mockStartServer).toHaveBeenCalledWith(expectedCall);
|
|
243
264
|
});
|
|
244
265
|
});
|
|
266
|
+
describe('--context option', () => {
|
|
267
|
+
it('rejects negative values', async () => {
|
|
268
|
+
const program = new Command();
|
|
269
|
+
program
|
|
270
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
271
|
+
.argument('[compare-with]', 'compare-with')
|
|
272
|
+
.option('--context <lines>', 'context', parseInt)
|
|
273
|
+
.action(async (commitish, _compareWith, options) => {
|
|
274
|
+
if (options.context !== undefined &&
|
|
275
|
+
(!Number.isInteger(options.context) || options.context < 0)) {
|
|
276
|
+
console.error('Error: --context must be a non-negative integer');
|
|
277
|
+
process.exit(1);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
await startServer({
|
|
281
|
+
targetCommitish: commitish,
|
|
282
|
+
baseCommitish: `${commitish}^`,
|
|
283
|
+
contextLines: options.context,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
await program.parseAsync(['--context', '-1'], { from: 'user' });
|
|
287
|
+
expect(console.error).toHaveBeenCalledWith('Error: --context must be a non-negative integer');
|
|
288
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
289
|
+
expect(mockStartServer).not.toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
it('rejects --context with --pr', async () => {
|
|
292
|
+
const prUrl = 'https://github.com/owner/repo/pull/123';
|
|
293
|
+
const program = new Command();
|
|
294
|
+
program
|
|
295
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
296
|
+
.argument('[compare-with]', 'compare-with')
|
|
297
|
+
.option('--context <lines>', 'context', parseInt)
|
|
298
|
+
.option('--pr <url>', 'pr')
|
|
299
|
+
.action(async (_commitish, _compareWith, options) => {
|
|
300
|
+
if (options.pr && options.context !== undefined) {
|
|
301
|
+
console.error('Error: --context option cannot be used with --pr');
|
|
302
|
+
process.exit(1);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await startServer({
|
|
306
|
+
stdinDiff: getPrPatch(options.pr),
|
|
307
|
+
contextLines: options.context,
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
await program.parseAsync(['--pr', prUrl, '--context', '3'], { from: 'user' });
|
|
311
|
+
expect(console.error).toHaveBeenCalledWith('Error: --context option cannot be used with --pr');
|
|
312
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
313
|
+
expect(mockGetPrPatch).not.toHaveBeenCalled();
|
|
314
|
+
expect(mockStartServer).not.toHaveBeenCalled();
|
|
315
|
+
});
|
|
316
|
+
it('rejects --context with stdin diff', async () => {
|
|
317
|
+
const program = new Command();
|
|
318
|
+
program
|
|
319
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
320
|
+
.argument('[compare-with]', 'compare-with')
|
|
321
|
+
.option('--context <lines>', 'context', parseInt)
|
|
322
|
+
.option('--tui', 'tui')
|
|
323
|
+
.action(async (commitish, _compareWith, options) => {
|
|
324
|
+
const readFromStdin = shouldReadStdin({
|
|
325
|
+
commitish,
|
|
326
|
+
hasPositionalArgs: program.args.length > 0,
|
|
327
|
+
hasPrOption: false,
|
|
328
|
+
hasTuiOption: Boolean(options.tui),
|
|
329
|
+
});
|
|
330
|
+
if (readFromStdin && options.context !== undefined) {
|
|
331
|
+
console.error('Error: --context option cannot be used with stdin diff');
|
|
332
|
+
process.exit(1);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
await startServer({
|
|
336
|
+
stdinDiff: 'diff --git a/file.ts b/file.ts',
|
|
337
|
+
contextLines: options.context,
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
await program.parseAsync(['-', '--context', '3'], { from: 'user' });
|
|
341
|
+
expect(console.error).toHaveBeenCalledWith('Error: --context option cannot be used with stdin diff');
|
|
342
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
343
|
+
expect(mockStartServer).not.toHaveBeenCalled();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
245
346
|
describe('Version option', () => {
|
|
246
347
|
it('supports --version flag', async () => {
|
|
247
348
|
const program = new Command();
|
|
@@ -409,14 +510,38 @@ describe('CLI index.ts', () => {
|
|
|
409
510
|
});
|
|
410
511
|
});
|
|
411
512
|
describe('GitHub PR integration', () => {
|
|
412
|
-
it('loads PR patch
|
|
513
|
+
it('loads PR patch, appends manual comments after PR imports, and starts server with stdin diff', async () => {
|
|
413
514
|
const prUrl = 'https://github.com/owner/repo/pull/123';
|
|
414
515
|
const prPatch = 'diff --git a/file.ts b/file.ts\nindex 1111111..2222222 100644\n';
|
|
516
|
+
const prCommentImports = [
|
|
517
|
+
{
|
|
518
|
+
type: 'thread',
|
|
519
|
+
id: 'PR_COMMENT_1',
|
|
520
|
+
filePath: 'src/example.ts',
|
|
521
|
+
position: { side: 'new', line: 10 },
|
|
522
|
+
body: 'Imported PR thread',
|
|
523
|
+
author: 'octocat',
|
|
524
|
+
createdAt: '2026-03-25T09:00:00Z',
|
|
525
|
+
updatedAt: '2026-03-25T09:05:00Z',
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
type: 'reply',
|
|
529
|
+
id: 'PR_COMMENT_2',
|
|
530
|
+
filePath: 'src/example.ts',
|
|
531
|
+
position: { side: 'new', line: 10 },
|
|
532
|
+
body: 'Imported PR reply',
|
|
533
|
+
author: 'hubot',
|
|
534
|
+
createdAt: '2026-03-25T09:10:00Z',
|
|
535
|
+
updatedAt: '2026-03-25T09:12:00Z',
|
|
536
|
+
},
|
|
537
|
+
];
|
|
415
538
|
mockGetPrPatch.mockReturnValue(prPatch);
|
|
539
|
+
mockGetPrCommentImports.mockResolvedValue(prCommentImports);
|
|
416
540
|
const program = new Command();
|
|
417
541
|
program
|
|
418
542
|
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
419
543
|
.argument('[compare-with]', 'compare-with')
|
|
544
|
+
.option('--comment <json>', 'comment', (value, previous = []) => [...previous, value], [])
|
|
420
545
|
.option('--port <port>', 'port', parseInt)
|
|
421
546
|
.option('--host <host>', 'host', '')
|
|
422
547
|
.option('--no-open', 'no-open')
|
|
@@ -424,11 +549,15 @@ describe('CLI index.ts', () => {
|
|
|
424
549
|
.option('--tui', 'tui')
|
|
425
550
|
.option('--pr <url>', 'pr')
|
|
426
551
|
.action(async (commitish, _compareWith, options) => {
|
|
552
|
+
const manualCommentImports = actualParseCommentOptions(options.comment);
|
|
553
|
+
let commentImports = manualCommentImports;
|
|
427
554
|
if (options.pr) {
|
|
428
555
|
if (commitish !== 'HEAD' || _compareWith) {
|
|
429
556
|
console.error('Error: --pr option cannot be used with positional arguments');
|
|
430
557
|
process.exit(1);
|
|
431
558
|
}
|
|
559
|
+
const importedPrComments = await getPrCommentImports(options.pr);
|
|
560
|
+
commentImports = [...importedPrComments, ...manualCommentImports];
|
|
432
561
|
}
|
|
433
562
|
await startServer({
|
|
434
563
|
stdinDiff: getPrPatch(options.pr),
|
|
@@ -436,10 +565,82 @@ describe('CLI index.ts', () => {
|
|
|
436
565
|
host: options.host,
|
|
437
566
|
openBrowser: options.open,
|
|
438
567
|
mode: options.mode,
|
|
568
|
+
commentImports,
|
|
439
569
|
});
|
|
440
570
|
});
|
|
441
|
-
await program.parseAsync([
|
|
571
|
+
await program.parseAsync([
|
|
572
|
+
'--pr',
|
|
573
|
+
prUrl,
|
|
574
|
+
'--comment',
|
|
575
|
+
'{"type":"reply","filePath":"src/example.ts","position":{"side":"new","line":10},"body":"Manual reply"}',
|
|
576
|
+
], { from: 'user' });
|
|
442
577
|
expect(mockGetPrPatch).toHaveBeenCalledWith(prUrl);
|
|
578
|
+
expect(mockGetPrCommentImports).toHaveBeenCalledWith(prUrl);
|
|
579
|
+
expect(mockStartServer).toHaveBeenCalledWith({
|
|
580
|
+
stdinDiff: prPatch,
|
|
581
|
+
preferredPort: undefined,
|
|
582
|
+
host: '',
|
|
583
|
+
openBrowser: true,
|
|
584
|
+
mode: 'split',
|
|
585
|
+
commentImports: [
|
|
586
|
+
...prCommentImports,
|
|
587
|
+
{
|
|
588
|
+
type: 'reply',
|
|
589
|
+
id: undefined,
|
|
590
|
+
filePath: 'src/example.ts',
|
|
591
|
+
position: { side: 'new', line: 10 },
|
|
592
|
+
body: 'Manual reply',
|
|
593
|
+
author: undefined,
|
|
594
|
+
createdAt: undefined,
|
|
595
|
+
updatedAt: undefined,
|
|
596
|
+
codeSnapshot: undefined,
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
it('continues with patch only when PR comment import fetch fails', async () => {
|
|
602
|
+
const prUrl = 'https://github.com/owner/repo/pull/123';
|
|
603
|
+
const prPatch = 'diff --git a/file.ts b/file.ts\nindex 1111111..2222222 100644\n';
|
|
604
|
+
mockGetPrPatch.mockReturnValue(prPatch);
|
|
605
|
+
mockGetPrCommentImports.mockRejectedValue(new Error('gh api graphql failed'));
|
|
606
|
+
const program = new Command();
|
|
607
|
+
program
|
|
608
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
609
|
+
.argument('[compare-with]', 'compare-with')
|
|
610
|
+
.option('--comment <json>', 'comment', (value, previous = []) => [...previous, value], [])
|
|
611
|
+
.option('--port <port>', 'port', parseInt)
|
|
612
|
+
.option('--host <host>', 'host', '')
|
|
613
|
+
.option('--no-open', 'no-open')
|
|
614
|
+
.option('--mode <mode>', 'mode', normalizeDiffViewMode, DEFAULT_DIFF_VIEW_MODE)
|
|
615
|
+
.option('--tui', 'tui')
|
|
616
|
+
.option('--pr <url>', 'pr')
|
|
617
|
+
.action(async (commitish, _compareWith, options) => {
|
|
618
|
+
const manualCommentImports = actualParseCommentOptions(options.comment);
|
|
619
|
+
let commentImports = manualCommentImports;
|
|
620
|
+
if (options.pr) {
|
|
621
|
+
if (commitish !== 'HEAD' || _compareWith) {
|
|
622
|
+
console.error('Error: --pr option cannot be used with positional arguments');
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
const importedPrComments = await getPrCommentImports(options.pr);
|
|
627
|
+
commentImports = [...importedPrComments, ...manualCommentImports];
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
console.warn(`Warning: Failed to load PR review comments: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
await startServer({
|
|
634
|
+
stdinDiff: getPrPatch(options.pr),
|
|
635
|
+
preferredPort: options.port,
|
|
636
|
+
host: options.host,
|
|
637
|
+
openBrowser: options.open,
|
|
638
|
+
mode: options.mode,
|
|
639
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
await program.parseAsync(['--pr', prUrl], { from: 'user' });
|
|
643
|
+
expect(console.warn).toHaveBeenCalledWith('Warning: Failed to load PR review comments: gh api graphql failed');
|
|
443
644
|
expect(mockStartServer).toHaveBeenCalledWith({
|
|
444
645
|
stdinDiff: prPatch,
|
|
445
646
|
preferredPort: undefined,
|
|
@@ -503,6 +704,95 @@ describe('CLI index.ts', () => {
|
|
|
503
704
|
expect(mockStartServer).not.toHaveBeenCalled();
|
|
504
705
|
});
|
|
505
706
|
});
|
|
707
|
+
describe('--comment option', () => {
|
|
708
|
+
it('passes parsed comment imports to startServer', async () => {
|
|
709
|
+
const program = new Command();
|
|
710
|
+
program
|
|
711
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
712
|
+
.option('--comment <json>', 'comment', (value, previous = []) => [...previous, value], [])
|
|
713
|
+
.option('--port <port>', 'port', parseInt)
|
|
714
|
+
.option('--host <host>', 'host', '')
|
|
715
|
+
.option('--no-open', 'no-open')
|
|
716
|
+
.option('--mode <mode>', 'mode', normalizeDiffViewMode, DEFAULT_DIFF_VIEW_MODE)
|
|
717
|
+
.action(async (commitish, options) => {
|
|
718
|
+
const commentImports = actualParseCommentOptions(options.comment);
|
|
719
|
+
await startServer({
|
|
720
|
+
targetCommitish: commitish,
|
|
721
|
+
baseCommitish: `${commitish}^`,
|
|
722
|
+
preferredPort: options.port,
|
|
723
|
+
host: options.host,
|
|
724
|
+
openBrowser: options.open,
|
|
725
|
+
mode: options.mode,
|
|
726
|
+
commentImports,
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
await program.parseAsync([
|
|
730
|
+
'--comment',
|
|
731
|
+
'{"type":"thread","filePath":"src/example.ts","position":{"side":"new","line":10},"body":"Imported comment"}',
|
|
732
|
+
], { from: 'user' });
|
|
733
|
+
expect(mockStartServer).toHaveBeenCalledWith({
|
|
734
|
+
targetCommitish: 'HEAD',
|
|
735
|
+
baseCommitish: 'HEAD^',
|
|
736
|
+
preferredPort: undefined,
|
|
737
|
+
host: '',
|
|
738
|
+
openBrowser: true,
|
|
739
|
+
mode: 'split',
|
|
740
|
+
commentImports: [
|
|
741
|
+
{
|
|
742
|
+
type: 'thread',
|
|
743
|
+
id: undefined,
|
|
744
|
+
filePath: 'src/example.ts',
|
|
745
|
+
position: { side: 'new', line: 10 },
|
|
746
|
+
body: 'Imported comment',
|
|
747
|
+
author: undefined,
|
|
748
|
+
createdAt: undefined,
|
|
749
|
+
updatedAt: undefined,
|
|
750
|
+
codeSnapshot: undefined,
|
|
751
|
+
},
|
|
752
|
+
],
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
it('rejects --comment with --tui', async () => {
|
|
756
|
+
const program = new Command();
|
|
757
|
+
program
|
|
758
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
759
|
+
.option('--comment <json>', 'comment', (value, previous = []) => [...previous, value], [])
|
|
760
|
+
.option('--tui', 'tui')
|
|
761
|
+
.action(async (_commitish, options) => {
|
|
762
|
+
const commentImports = actualParseCommentOptions(options.comment);
|
|
763
|
+
if (options.tui && commentImports.length > 0) {
|
|
764
|
+
console.error('Error: --comment option cannot be used with --tui');
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
await program.parseAsync([
|
|
769
|
+
'--tui',
|
|
770
|
+
'--comment',
|
|
771
|
+
'{"type":"thread","filePath":"src/example.ts","position":{"side":"new","line":10},"body":"Imported comment"}',
|
|
772
|
+
], { from: 'user' });
|
|
773
|
+
expect(console.error).toHaveBeenCalledWith('Error: --comment option cannot be used with --tui');
|
|
774
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
775
|
+
});
|
|
776
|
+
it('reports invalid comment json before starting the server', async () => {
|
|
777
|
+
const program = new Command();
|
|
778
|
+
program
|
|
779
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
780
|
+
.option('--comment <json>', 'comment', (value, previous = []) => [...previous, value], [])
|
|
781
|
+
.action(async (_commitish, options) => {
|
|
782
|
+
try {
|
|
783
|
+
actualParseCommentOptions(options.comment);
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Invalid --comment value'}`);
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
await program.parseAsync(['--comment', '{'], { from: 'user' });
|
|
791
|
+
expect(console.error).toHaveBeenCalledWith('Error: Invalid --comment JSON');
|
|
792
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
793
|
+
expect(mockStartServer).not.toHaveBeenCalled();
|
|
794
|
+
});
|
|
795
|
+
});
|
|
506
796
|
describe('Clean flag functionality', () => {
|
|
507
797
|
it('displays clean message when flag is used', async () => {
|
|
508
798
|
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
@@ -872,6 +1162,42 @@ describe('CLI index.ts', () => {
|
|
|
872
1162
|
},
|
|
873
1163
|
});
|
|
874
1164
|
});
|
|
1165
|
+
it('passes context option to TUI app', async () => {
|
|
1166
|
+
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
1167
|
+
const program = new Command();
|
|
1168
|
+
program
|
|
1169
|
+
.argument('[commit-ish]', 'commit-ish', 'HEAD')
|
|
1170
|
+
.argument('[compare-with]', 'compare-with')
|
|
1171
|
+
.option('--port <port>', 'port', parseInt)
|
|
1172
|
+
.option('--host <host>', 'host', '')
|
|
1173
|
+
.option('--no-open', 'no-open')
|
|
1174
|
+
.option('--mode <mode>', 'mode', normalizeDiffViewMode, DEFAULT_DIFF_VIEW_MODE)
|
|
1175
|
+
.option('--context <lines>', 'context', parseInt)
|
|
1176
|
+
.option('--tui', 'tui')
|
|
1177
|
+
.option('--pr <url>', 'pr')
|
|
1178
|
+
.action(async (commitish, _compareWith, options) => {
|
|
1179
|
+
if (options.tui) {
|
|
1180
|
+
const { render } = await import('ink');
|
|
1181
|
+
const { default: TuiApp } = await import('../tui/App.js');
|
|
1182
|
+
render(React.createElement(TuiApp, {
|
|
1183
|
+
targetCommitish: commitish,
|
|
1184
|
+
baseCommitish: commitish + '^',
|
|
1185
|
+
mode: options.mode,
|
|
1186
|
+
contextLines: options.context,
|
|
1187
|
+
}));
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
await program.parseAsync(['--tui', '--context', '2'], { from: 'user' });
|
|
1191
|
+
expect(mockRender).toHaveBeenCalledWith({
|
|
1192
|
+
component: mockTuiApp,
|
|
1193
|
+
props: {
|
|
1194
|
+
targetCommitish: 'HEAD',
|
|
1195
|
+
baseCommitish: 'HEAD^',
|
|
1196
|
+
mode: 'split',
|
|
1197
|
+
contextLines: 2,
|
|
1198
|
+
},
|
|
1199
|
+
});
|
|
1200
|
+
});
|
|
875
1201
|
it('passes mode option to TUI app', async () => {
|
|
876
1202
|
mockFindUntrackedFiles.mockResolvedValue([]);
|
|
877
1203
|
const program = new Command();
|
package/dist/cli/utils.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Stats } from 'node:fs';
|
|
2
2
|
import type { SimpleGit } from 'simple-git';
|
|
3
|
+
import type { CommentImport } from '../types/diff.js';
|
|
3
4
|
type StdinStat = Pick<Stats, 'isFIFO' | 'isFile' | 'isSocket'>;
|
|
4
5
|
type StdinSource = 'pipe' | 'file' | 'socket' | 'tty';
|
|
5
6
|
export declare function detectStdinSource(stdinStat?: StdinStat): StdinSource;
|
|
@@ -15,14 +16,7 @@ export declare function getGitRoot(): string;
|
|
|
15
16
|
export declare function validateCommitish(commitish: string): boolean;
|
|
16
17
|
export declare function shortHash(hash: string): string;
|
|
17
18
|
export declare function createCommitRangeString(baseHash: string, targetHash: string): string;
|
|
18
|
-
|
|
19
|
-
owner: string;
|
|
20
|
-
repo: string;
|
|
21
|
-
pullNumber: number;
|
|
22
|
-
hostname: string;
|
|
23
|
-
}
|
|
24
|
-
export declare function parseGitHubPrUrl(url: string): PullRequestInfo | null;
|
|
25
|
-
export declare function getPrPatch(prArg: string): string;
|
|
19
|
+
export declare function parseCommentOptions(commentValues: string[]): CommentImport[];
|
|
26
20
|
export declare function validateDiffArguments(targetCommitish: string, baseCommitish?: string): {
|
|
27
21
|
valid: boolean;
|
|
28
22
|
error?: string;
|
package/dist/cli/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
2
|
import { fstatSync } from 'node:fs';
|
|
3
3
|
import { createInterface } from 'readline/promises';
|
|
4
|
+
import { parseCommentImportValue } from '../utils/commentImports.js';
|
|
4
5
|
export function detectStdinSource(stdinStat = fstatSync(0)) {
|
|
5
6
|
if (stdinStat.isFIFO()) {
|
|
6
7
|
return 'pipe';
|
|
@@ -129,48 +130,8 @@ export function shortHash(hash) {
|
|
|
129
130
|
export function createCommitRangeString(baseHash, targetHash) {
|
|
130
131
|
return `${baseHash}...${targetHash}`;
|
|
131
132
|
}
|
|
132
|
-
export function
|
|
133
|
-
|
|
134
|
-
const urlObj = new URL(url);
|
|
135
|
-
// Allow any hostname for GitHub Enterprise support
|
|
136
|
-
// Just validate the path structure
|
|
137
|
-
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
|
138
|
-
if (pathParts.length < 4 || pathParts[2] !== 'pull') {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
const owner = pathParts[0];
|
|
142
|
-
const repo = pathParts[1];
|
|
143
|
-
const pullNumber = parseInt(pathParts[3], 10);
|
|
144
|
-
if (isNaN(pullNumber)) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
return { owner, repo, pullNumber, hostname: urlObj.hostname };
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
export function getPrPatch(prArg) {
|
|
154
|
-
try {
|
|
155
|
-
const patch = execFileSync('gh', ['pr', 'diff', prArg], {
|
|
156
|
-
encoding: 'utf8',
|
|
157
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
158
|
-
});
|
|
159
|
-
if (!patch.trim()) {
|
|
160
|
-
throw new Error('No diff content returned from gh pr diff');
|
|
161
|
-
}
|
|
162
|
-
return patch;
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
const stderr = error.stderr;
|
|
166
|
-
const stderrText = typeof stderr === 'string'
|
|
167
|
-
? stderr.trim()
|
|
168
|
-
: Buffer.isBuffer(stderr)
|
|
169
|
-
? stderr.toString('utf8').trim()
|
|
170
|
-
: '';
|
|
171
|
-
const message = stderrText || (error instanceof Error ? error.message : 'Unknown error while running gh');
|
|
172
|
-
throw new Error(`${message}\nTry: gh auth login`);
|
|
173
|
-
}
|
|
133
|
+
export function parseCommentOptions(commentValues) {
|
|
134
|
+
return commentValues.flatMap((value) => parseCommentImportValue(value));
|
|
174
135
|
}
|
|
175
136
|
export function validateDiffArguments(targetCommitish, baseCommitish) {
|
|
176
137
|
// Validate target commitish format
|