@aaronshaf/ger 2.0.3 → 2.0.5
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/package.json +1 -1
- package/src/cli/commands/abandon.ts +28 -2
- package/src/cli/commands/add-reviewer.ts +37 -5
- package/src/cli/commands/comment.ts +93 -6
- package/src/cli/commands/comments.ts +34 -2
- package/src/cli/commands/diff.ts +13 -1
- package/src/cli/commands/groups-members.ts +28 -3
- package/src/cli/commands/groups-show.ts +38 -2
- package/src/cli/commands/groups.ts +32 -3
- package/src/cli/commands/incoming.ts +21 -1
- package/src/cli/commands/install-hook.ts +27 -6
- package/src/cli/commands/mine.ts +19 -1
- package/src/cli/commands/projects.ts +22 -2
- package/src/cli/commands/rebase.ts +19 -2
- package/src/cli/commands/remove-reviewer.ts +32 -4
- package/src/cli/commands/restore.ts +15 -1
- package/src/cli/commands/search.ts +22 -1
- package/src/cli/commands/status.ts +11 -1
- package/src/cli/commands/submit.ts +30 -2
- package/src/cli/commands/topic.ts +33 -3
- package/src/cli/commands/vote.ts +15 -1
- package/src/cli/commands/workspace.ts +23 -4
- package/src/cli/register-commands.ts +40 -22
- package/src/cli/register-group-commands.ts +17 -2
- package/src/cli/register-reviewer-commands.ts +16 -2
- package/src/schemas/gerrit.ts +5 -6
- package/src/services/commit-hook.ts +6 -8
- package/tests/mine.test.ts +56 -0
|
@@ -33,10 +33,16 @@ import { sanitizeCDATA } from '@/utils/shell-safety'
|
|
|
33
33
|
import { registerGroupCommands } from './register-group-commands'
|
|
34
34
|
import { registerReviewerCommands } from './register-reviewer-commands'
|
|
35
35
|
|
|
36
|
-
// Helper function to output error in plain text or XML format
|
|
37
|
-
function outputError(
|
|
36
|
+
// Helper function to output error in plain text, JSON, or XML format
|
|
37
|
+
function outputError(
|
|
38
|
+
error: unknown,
|
|
39
|
+
options: { xml?: boolean; json?: boolean },
|
|
40
|
+
resultTag: string,
|
|
41
|
+
): void {
|
|
38
42
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
39
|
-
if (options.
|
|
43
|
+
if (options.json) {
|
|
44
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
45
|
+
} else if (options.xml) {
|
|
40
46
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
41
47
|
console.log(`<${resultTag}>`)
|
|
42
48
|
console.log(` <status>error</status>`)
|
|
@@ -50,9 +56,13 @@ function outputError(error: unknown, options: { xml?: boolean }, resultTag: stri
|
|
|
50
56
|
// Helper function to execute Effect with standard error handling
|
|
51
57
|
async function executeEffect<E>(
|
|
52
58
|
effect: Effect.Effect<void, E, never>,
|
|
53
|
-
options: { xml?: boolean },
|
|
59
|
+
options: { xml?: boolean; json?: boolean },
|
|
54
60
|
resultTag: string,
|
|
55
61
|
): Promise<void> {
|
|
62
|
+
if (options.xml && options.json) {
|
|
63
|
+
outputError(new Error('--xml and --json are mutually exclusive'), options, resultTag)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
56
66
|
try {
|
|
57
67
|
await Effect.runPromise(effect)
|
|
58
68
|
} catch (error) {
|
|
@@ -83,6 +93,7 @@ export function registerCommands(program: Command): void {
|
|
|
83
93
|
.command('status')
|
|
84
94
|
.description('Check connection status')
|
|
85
95
|
.option('--xml', 'XML output for LLM consumption')
|
|
96
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
86
97
|
.action(async (options) => {
|
|
87
98
|
await executeEffect(
|
|
88
99
|
statusCommand(options).pipe(
|
|
@@ -105,9 +116,14 @@ export function registerCommands(program: Command): void {
|
|
|
105
116
|
'Line number in the NEW version of the file (not diff line numbers)',
|
|
106
117
|
parseInt,
|
|
107
118
|
)
|
|
119
|
+
.option(
|
|
120
|
+
'--reply-to <comment-id>',
|
|
121
|
+
'Reply to a comment thread (requires --file and --line; resolves thread by default)',
|
|
122
|
+
)
|
|
108
123
|
.option('--unresolved', 'Mark comment as unresolved (requires human attention)')
|
|
109
124
|
.option('--batch', 'Read batch comments from stdin as JSON (see examples below)')
|
|
110
125
|
.option('--xml', 'XML output for LLM consumption')
|
|
126
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
111
127
|
.addHelpText('after', COMMENT_HELP_TEXT)
|
|
112
128
|
.action(async (changeId, options) => {
|
|
113
129
|
await executeEffect(
|
|
@@ -125,6 +141,7 @@ export function registerCommands(program: Command): void {
|
|
|
125
141
|
.command('diff <change-id>')
|
|
126
142
|
.description('Get diff for a change (accepts change number or Change-ID)')
|
|
127
143
|
.option('--xml', 'XML output for LLM consumption')
|
|
144
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
128
145
|
.option('--file <file>', 'Specific file to diff')
|
|
129
146
|
.option('--files-only', 'List changed files only')
|
|
130
147
|
.option('--format <format>', 'Output format (unified, json, files)')
|
|
@@ -144,6 +161,7 @@ export function registerCommands(program: Command): void {
|
|
|
144
161
|
.command('mine')
|
|
145
162
|
.description('Show your open changes')
|
|
146
163
|
.option('--xml', 'XML output for LLM consumption')
|
|
164
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
147
165
|
.action(async (options) => {
|
|
148
166
|
await executeEffect(
|
|
149
167
|
mineCommand(options).pipe(
|
|
@@ -160,6 +178,7 @@ export function registerCommands(program: Command): void {
|
|
|
160
178
|
.command('search [query]')
|
|
161
179
|
.description('Search changes using Gerrit query syntax')
|
|
162
180
|
.option('--xml', 'XML output for LLM consumption')
|
|
181
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
163
182
|
.option('-n, --limit <number>', 'Limit results (default: 25)')
|
|
164
183
|
.addHelpText('after', SEARCH_HELP_TEXT)
|
|
165
184
|
.action(async (query, options) => {
|
|
@@ -168,16 +187,17 @@ export function registerCommands(program: Command): void {
|
|
|
168
187
|
Effect.provide(ConfigServiceLive),
|
|
169
188
|
)
|
|
170
189
|
await Effect.runPromise(effect).catch((error: unknown) => {
|
|
171
|
-
|
|
190
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
191
|
+
if (options.json) {
|
|
192
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
193
|
+
} else if (options.xml) {
|
|
172
194
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
173
195
|
console.log(`<search_result>`)
|
|
174
196
|
console.log(` <status>error</status>`)
|
|
175
|
-
console.log(
|
|
176
|
-
` <error><![CDATA[${error instanceof Error ? error.message : String(error)}]]></error>`,
|
|
177
|
-
)
|
|
197
|
+
console.log(` <error><![CDATA[${errorMessage}]]></error>`)
|
|
178
198
|
console.log(`</search_result>`)
|
|
179
199
|
} else {
|
|
180
|
-
console.error('✗ Error:',
|
|
200
|
+
console.error('✗ Error:', errorMessage)
|
|
181
201
|
}
|
|
182
202
|
process.exit(1)
|
|
183
203
|
})
|
|
@@ -190,6 +210,7 @@ export function registerCommands(program: Command): void {
|
|
|
190
210
|
'Create or switch to a git worktree for a Gerrit change (accepts change number or Change-ID)',
|
|
191
211
|
)
|
|
192
212
|
.option('--xml', 'XML output for LLM consumption')
|
|
213
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
193
214
|
.action(async (changeId, options) => {
|
|
194
215
|
await executeEffect(
|
|
195
216
|
workspaceCommand(changeId, options).pipe(
|
|
@@ -206,6 +227,7 @@ export function registerCommands(program: Command): void {
|
|
|
206
227
|
.command('incoming')
|
|
207
228
|
.description('Show incoming changes for review (where you are a reviewer)')
|
|
208
229
|
.option('--xml', 'XML output for LLM consumption')
|
|
230
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
209
231
|
.option('-i, --interactive', 'Interactive mode with detailed view and diff')
|
|
210
232
|
.action(async (options) => {
|
|
211
233
|
await executeEffect(
|
|
@@ -226,6 +248,7 @@ export function registerCommands(program: Command): void {
|
|
|
226
248
|
)
|
|
227
249
|
.option('-m, --message <message>', 'Abandon message')
|
|
228
250
|
.option('--xml', 'XML output for LLM consumption')
|
|
251
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
229
252
|
.action(async (changeId, options) => {
|
|
230
253
|
await executeEffect(
|
|
231
254
|
abandonCommand(changeId, options).pipe(
|
|
@@ -243,6 +266,7 @@ export function registerCommands(program: Command): void {
|
|
|
243
266
|
.description('Restore an abandoned change (accepts change number or Change-ID)')
|
|
244
267
|
.option('-m, --message <message>', 'Restoration message')
|
|
245
268
|
.option('--xml', 'XML output for LLM consumption')
|
|
269
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
246
270
|
.action(async (changeId, options) => {
|
|
247
271
|
await executeEffect(
|
|
248
272
|
restoreCommand(changeId, options).pipe(
|
|
@@ -260,6 +284,7 @@ export function registerCommands(program: Command): void {
|
|
|
260
284
|
.description('Rebase a change onto target branch (auto-detects from HEAD if not provided)')
|
|
261
285
|
.option('--base <ref>', 'Base revision to rebase onto (default: target branch HEAD)')
|
|
262
286
|
.option('--xml', 'XML output for LLM consumption')
|
|
287
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
263
288
|
.action(async (changeId, options) => {
|
|
264
289
|
await executeEffect(
|
|
265
290
|
rebaseCommand(changeId, options).pipe(
|
|
@@ -276,6 +301,7 @@ export function registerCommands(program: Command): void {
|
|
|
276
301
|
.command('submit <change-id>')
|
|
277
302
|
.description('Submit a change for merging (accepts change number or Change-ID)')
|
|
278
303
|
.option('--xml', 'XML output for LLM consumption')
|
|
304
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
279
305
|
.action(async (changeId, options) => {
|
|
280
306
|
await executeEffect(
|
|
281
307
|
submitCommand(changeId, options).pipe(
|
|
@@ -293,6 +319,7 @@ export function registerCommands(program: Command): void {
|
|
|
293
319
|
.description('Get, set, or remove topic for a change (auto-detects from HEAD if not specified)')
|
|
294
320
|
.option('--delete', 'Remove the topic from the change')
|
|
295
321
|
.option('--xml', 'XML output for LLM consumption')
|
|
322
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
296
323
|
.addHelpText('after', TOPIC_HELP_TEXT)
|
|
297
324
|
.action(async (changeId, topic, options) => {
|
|
298
325
|
await executeEffect(
|
|
@@ -314,6 +341,7 @@ export function registerCommands(program: Command): void {
|
|
|
314
341
|
.option('--label <name> <value>', 'Custom label vote (can be used multiple times)')
|
|
315
342
|
.option('-m, --message <message>', 'Comment with vote')
|
|
316
343
|
.option('--xml', 'XML output for LLM consumption')
|
|
344
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
317
345
|
.action(async (changeId, options) => {
|
|
318
346
|
await executeEffect(
|
|
319
347
|
voteCommand(changeId, options).pipe(
|
|
@@ -334,6 +362,7 @@ export function registerCommands(program: Command): void {
|
|
|
334
362
|
.description('List Gerrit projects')
|
|
335
363
|
.option('--pattern <regex>', 'Filter projects by name pattern')
|
|
336
364
|
.option('--xml', 'XML output for LLM consumption')
|
|
365
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
337
366
|
.action(async (options) => {
|
|
338
367
|
await executeEffect(
|
|
339
368
|
projectsCommand(options).pipe(
|
|
@@ -355,6 +384,7 @@ export function registerCommands(program: Command): void {
|
|
|
355
384
|
'Show all comments on a change with diff context (accepts change number or Change-ID)',
|
|
356
385
|
)
|
|
357
386
|
.option('--xml', 'XML output for LLM consumption')
|
|
387
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
358
388
|
.action(async (changeId, options) => {
|
|
359
389
|
await executeEffect(
|
|
360
390
|
commentsCommand(changeId, options).pipe(
|
|
@@ -523,6 +553,7 @@ Note:
|
|
|
523
553
|
.description('Install the Gerrit commit-msg hook for automatic Change-Id generation')
|
|
524
554
|
.option('--force', 'Overwrite existing hook')
|
|
525
555
|
.option('--xml', 'XML output for LLM consumption')
|
|
556
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
526
557
|
.addHelpText(
|
|
527
558
|
'after',
|
|
528
559
|
`
|
|
@@ -623,10 +654,8 @@ Note:
|
|
|
623
654
|
`
|
|
624
655
|
This command uses AI (claude CLI, gemini CLI, or opencode CLI) to review a Gerrit change.
|
|
625
656
|
It performs a two-stage review process:
|
|
626
|
-
|
|
627
657
|
1. Generates inline comments for specific code issues
|
|
628
658
|
2. Generates an overall review comment
|
|
629
|
-
|
|
630
659
|
By default, the review is only displayed in the terminal.
|
|
631
660
|
Use --comment to post the review to Gerrit (with confirmation prompts).
|
|
632
661
|
Use --comment --yes to post without confirmation.
|
|
@@ -636,22 +665,11 @@ Requirements:
|
|
|
636
665
|
- Gerrit credentials must be configured (run 'ger setup' first)
|
|
637
666
|
|
|
638
667
|
Examples:
|
|
639
|
-
# Review a change using change number (display only)
|
|
640
668
|
$ ger review 12345
|
|
641
|
-
|
|
642
|
-
# Review using Change-ID
|
|
643
669
|
$ ger review If5a3ae8cb5a107e187447802358417f311d0c4b1
|
|
644
|
-
|
|
645
|
-
# Review and prompt to post comments
|
|
646
670
|
$ ger review 12345 --comment
|
|
647
|
-
|
|
648
|
-
# Review and auto-post comments without prompting
|
|
649
671
|
$ ger review 12345 --comment --yes
|
|
650
|
-
|
|
651
|
-
# Use specific AI tool
|
|
652
672
|
$ ger review 12345 --tool gemini
|
|
653
|
-
|
|
654
|
-
# Show debug output to troubleshoot issues
|
|
655
673
|
$ ger review 12345 --debug
|
|
656
674
|
|
|
657
675
|
Note: Both change number (e.g., 12345) and Change-ID (e.g., If5a3ae8...) formats are accepted
|
|
@@ -9,14 +9,26 @@ import { groupsMembersCommand } from './commands/groups-members'
|
|
|
9
9
|
// Helper function to execute Effect with standard error handling
|
|
10
10
|
async function executeEffect<E>(
|
|
11
11
|
effect: Effect.Effect<void, E, never>,
|
|
12
|
-
options: { xml?: boolean },
|
|
12
|
+
options: { xml?: boolean; json?: boolean },
|
|
13
13
|
resultTag: string,
|
|
14
14
|
): Promise<void> {
|
|
15
|
+
if (options.xml && options.json) {
|
|
16
|
+
console.log(
|
|
17
|
+
JSON.stringify(
|
|
18
|
+
{ status: 'error', error: '--xml and --json are mutually exclusive' },
|
|
19
|
+
null,
|
|
20
|
+
2,
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
15
25
|
try {
|
|
16
26
|
await Effect.runPromise(effect)
|
|
17
27
|
} catch (error) {
|
|
18
28
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
19
|
-
if (options.
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
31
|
+
} else if (options.xml) {
|
|
20
32
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
21
33
|
console.log(`<${resultTag}>`)
|
|
22
34
|
console.log(` <status>error</status>`)
|
|
@@ -43,6 +55,7 @@ export function registerGroupCommands(program: Command): void {
|
|
|
43
55
|
.option('--user <account>', 'Show groups a user belongs to')
|
|
44
56
|
.option('--limit <n>', 'Maximum number of results (default: 25)')
|
|
45
57
|
.option('--xml', 'XML output for LLM consumption')
|
|
58
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
46
59
|
.action(async (options) => {
|
|
47
60
|
await executeEffect(
|
|
48
61
|
groupsCommand(options).pipe(
|
|
@@ -59,6 +72,7 @@ export function registerGroupCommands(program: Command): void {
|
|
|
59
72
|
.command('groups-show <group-id>')
|
|
60
73
|
.description('Show detailed information about a Gerrit group')
|
|
61
74
|
.option('--xml', 'XML output for LLM consumption')
|
|
75
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
62
76
|
.action(async (groupId, options) => {
|
|
63
77
|
await executeEffect(
|
|
64
78
|
groupsShowCommand(groupId, options).pipe(
|
|
@@ -75,6 +89,7 @@ export function registerGroupCommands(program: Command): void {
|
|
|
75
89
|
.command('groups-members <group-id>')
|
|
76
90
|
.description('List all members of a Gerrit group')
|
|
77
91
|
.option('--xml', 'XML output for LLM consumption')
|
|
92
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
78
93
|
.action(async (groupId, options) => {
|
|
79
94
|
await executeEffect(
|
|
80
95
|
groupsMembersCommand(groupId, options).pipe(
|
|
@@ -8,14 +8,26 @@ import { removeReviewerCommand } from './commands/remove-reviewer'
|
|
|
8
8
|
// Helper function to execute Effect with standard error handling
|
|
9
9
|
async function executeEffect<E>(
|
|
10
10
|
effect: Effect.Effect<void, E, never>,
|
|
11
|
-
options: { xml?: boolean },
|
|
11
|
+
options: { xml?: boolean; json?: boolean },
|
|
12
12
|
resultTag: string,
|
|
13
13
|
): Promise<void> {
|
|
14
|
+
if (options.xml && options.json) {
|
|
15
|
+
console.log(
|
|
16
|
+
JSON.stringify(
|
|
17
|
+
{ status: 'error', error: '--xml and --json are mutually exclusive' },
|
|
18
|
+
null,
|
|
19
|
+
2,
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
14
24
|
try {
|
|
15
25
|
await Effect.runPromise(effect)
|
|
16
26
|
} catch (error) {
|
|
17
27
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
18
|
-
if (options.
|
|
28
|
+
if (options.json) {
|
|
29
|
+
console.log(JSON.stringify({ status: 'error', error: errorMessage }, null, 2))
|
|
30
|
+
} else if (options.xml) {
|
|
19
31
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
20
32
|
console.log(`<${resultTag}>`)
|
|
21
33
|
console.log(` <status>error</status>`)
|
|
@@ -44,6 +56,7 @@ export function registerReviewerCommands(program: Command): void {
|
|
|
44
56
|
'Notification level: none, owner, owner_reviewers, all (default: all)',
|
|
45
57
|
)
|
|
46
58
|
.option('--xml', 'XML output for LLM consumption')
|
|
59
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
47
60
|
.addHelpText(
|
|
48
61
|
'after',
|
|
49
62
|
`
|
|
@@ -76,6 +89,7 @@ Examples:
|
|
|
76
89
|
'Notification level: none, owner, owner_reviewers, all (default: all)',
|
|
77
90
|
)
|
|
78
91
|
.option('--xml', 'XML output for LLM consumption')
|
|
92
|
+
.option('--json', 'JSON output for programmatic consumption')
|
|
79
93
|
.addHelpText(
|
|
80
94
|
'after',
|
|
81
95
|
`
|
package/src/schemas/gerrit.ts
CHANGED
|
@@ -308,6 +308,7 @@ export const ReviewInput: Schema.Schema<{
|
|
|
308
308
|
readonly message: string
|
|
309
309
|
readonly side?: 'PARENT' | 'REVISION'
|
|
310
310
|
readonly unresolved?: boolean
|
|
311
|
+
readonly in_reply_to?: string
|
|
311
312
|
}>
|
|
312
313
|
>
|
|
313
314
|
}> = Schema.Struct({
|
|
@@ -330,6 +331,7 @@ export const ReviewInput: Schema.Schema<{
|
|
|
330
331
|
message: Schema.String,
|
|
331
332
|
side: Schema.optional(Schema.Literal('PARENT', 'REVISION')),
|
|
332
333
|
unresolved: Schema.optional(Schema.Boolean),
|
|
334
|
+
in_reply_to: Schema.optional(Schema.String),
|
|
333
335
|
}),
|
|
334
336
|
),
|
|
335
337
|
}),
|
|
@@ -519,17 +521,14 @@ export type DiffOptions = Schema.Schema.Type<typeof DiffOptions>
|
|
|
519
521
|
// Command options schemas
|
|
520
522
|
export const DiffCommandOptions: Schema.Schema<{
|
|
521
523
|
readonly xml?: boolean
|
|
524
|
+
readonly json?: boolean
|
|
522
525
|
readonly file?: string
|
|
523
526
|
readonly filesOnly?: boolean
|
|
524
527
|
readonly format?: 'unified' | 'json' | 'files'
|
|
525
528
|
}> = Schema.Struct({
|
|
526
529
|
xml: Schema.optional(Schema.Boolean),
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
Schema.minLength(1),
|
|
530
|
-
Schema.annotations({ description: 'File path for diff (relative to repo root)' }),
|
|
531
|
-
),
|
|
532
|
-
),
|
|
530
|
+
json: Schema.optional(Schema.Boolean),
|
|
531
|
+
file: Schema.optional(Schema.String.pipe(Schema.minLength(1))),
|
|
533
532
|
filesOnly: Schema.optional(Schema.Boolean),
|
|
534
533
|
format: Schema.optional(DiffFormat),
|
|
535
534
|
})
|
|
@@ -135,11 +135,9 @@ export const getHooksDir = (): string => {
|
|
|
135
135
|
export interface CommitHookServiceImpl {
|
|
136
136
|
readonly hasHook: () => Effect.Effect<boolean, NotGitRepoError>
|
|
137
137
|
readonly hasChangeId: (commit?: string) => Effect.Effect<boolean, NotGitRepoError>
|
|
138
|
-
readonly installHook: (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
ConfigServiceImpl
|
|
142
|
-
>
|
|
138
|
+
readonly installHook: (
|
|
139
|
+
quiet?: boolean,
|
|
140
|
+
) => Effect.Effect<void, HookInstallError | NotGitRepoError, ConfigServiceImpl>
|
|
143
141
|
readonly ensureChangeId: () => Effect.Effect<
|
|
144
142
|
void,
|
|
145
143
|
HookInstallError | MissingChangeIdError | NotGitRepoError,
|
|
@@ -161,7 +159,7 @@ const CommitHookServiceImplLive: CommitHookServiceImpl = {
|
|
|
161
159
|
catch: () => new NotGitRepoError({ message: 'Not in a git repository' }),
|
|
162
160
|
}),
|
|
163
161
|
|
|
164
|
-
installHook: () =>
|
|
162
|
+
installHook: (quiet = false) =>
|
|
165
163
|
Effect.gen(function* () {
|
|
166
164
|
const configService = yield* ConfigService
|
|
167
165
|
|
|
@@ -177,7 +175,7 @@ const CommitHookServiceImplLive: CommitHookServiceImpl = {
|
|
|
177
175
|
const normalizedHost = config.host.replace(/\/$/, '')
|
|
178
176
|
const hookUrl = `${normalizedHost}/tools/hooks/commit-msg`
|
|
179
177
|
|
|
180
|
-
yield* Console.log(`Installing commit-msg hook from ${config.host}...`)
|
|
178
|
+
if (!quiet) yield* Console.log(`Installing commit-msg hook from ${config.host}...`)
|
|
181
179
|
|
|
182
180
|
const hookContent = yield* Effect.tryPromise({
|
|
183
181
|
try: async () => {
|
|
@@ -236,7 +234,7 @@ const CommitHookServiceImplLive: CommitHookServiceImpl = {
|
|
|
236
234
|
}),
|
|
237
235
|
})
|
|
238
236
|
|
|
239
|
-
yield* Console.log('commit-msg hook installed successfully')
|
|
237
|
+
if (!quiet) yield* Console.log('commit-msg hook installed successfully')
|
|
240
238
|
}),
|
|
241
239
|
|
|
242
240
|
ensureChangeId: () =>
|
package/tests/mine.test.ts
CHANGED
|
@@ -125,6 +125,62 @@ describe('mine command', () => {
|
|
|
125
125
|
expect(output).toContain('</changes>')
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
+
test('should include labels in --json output', async () => {
|
|
129
|
+
const mockChanges: ChangeInfo[] = [
|
|
130
|
+
generateMockChange({
|
|
131
|
+
_number: 12345,
|
|
132
|
+
subject: 'Labeled change',
|
|
133
|
+
labels: {
|
|
134
|
+
'Code-Review': { value: 2 },
|
|
135
|
+
Verified: { value: -1 },
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
server.use(
|
|
141
|
+
http.get('*/a/changes/', () => {
|
|
142
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChanges)}`)
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
147
|
+
await Effect.runPromise(
|
|
148
|
+
mineCommand({ json: true }).pipe(
|
|
149
|
+
Effect.provide(GerritApiServiceLive),
|
|
150
|
+
Effect.provide(mockConfigLayer),
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
155
|
+
const parsed = JSON.parse(output)
|
|
156
|
+
const change = parsed.changes[0]
|
|
157
|
+
expect(change.labels).toBeDefined()
|
|
158
|
+
expect(change.labels['Code-Review'].value).toBe(2)
|
|
159
|
+
expect(change.labels['Verified'].value).toBe(-1)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('should omit labels key in --json output when change has no labels', async () => {
|
|
163
|
+
const mockChanges: ChangeInfo[] = [generateMockChange({ _number: 12345 })]
|
|
164
|
+
|
|
165
|
+
server.use(
|
|
166
|
+
http.get('*/a/changes/', () => {
|
|
167
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChanges)}`)
|
|
168
|
+
}),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
172
|
+
await Effect.runPromise(
|
|
173
|
+
mineCommand({ json: true }).pipe(
|
|
174
|
+
Effect.provide(GerritApiServiceLive),
|
|
175
|
+
Effect.provide(mockConfigLayer),
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
180
|
+
const parsed = JSON.parse(output)
|
|
181
|
+
expect(parsed.changes[0].labels).toBeUndefined()
|
|
182
|
+
})
|
|
183
|
+
|
|
128
184
|
test('should handle no changes gracefully', async () => {
|
|
129
185
|
server.use(
|
|
130
186
|
http.get('*/a/changes/', () => {
|