@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
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
|
4
4
|
interface AbandonOptions {
|
|
5
5
|
message?: string
|
|
6
6
|
xml?: boolean
|
|
7
|
+
json?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const abandonCommand = (
|
|
@@ -26,7 +27,20 @@ export const abandonCommand = (
|
|
|
26
27
|
// Perform the abandon
|
|
27
28
|
yield* gerritApi.abandonChange(changeId, options.message)
|
|
28
29
|
|
|
29
|
-
if (options.
|
|
30
|
+
if (options.json) {
|
|
31
|
+
console.log(
|
|
32
|
+
JSON.stringify(
|
|
33
|
+
{
|
|
34
|
+
status: 'success',
|
|
35
|
+
change_number: change._number,
|
|
36
|
+
subject: change.subject,
|
|
37
|
+
...(options.message ? { message: options.message } : {}),
|
|
38
|
+
},
|
|
39
|
+
null,
|
|
40
|
+
2,
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
} else if (options.xml) {
|
|
30
44
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
31
45
|
console.log(`<abandon_result>`)
|
|
32
46
|
console.log(` <status>success</status>`)
|
|
@@ -46,7 +60,19 @@ export const abandonCommand = (
|
|
|
46
60
|
// If we can't get change details, still try to abandon with just the ID
|
|
47
61
|
yield* gerritApi.abandonChange(changeId, options.message)
|
|
48
62
|
|
|
49
|
-
if (options.
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(
|
|
65
|
+
JSON.stringify(
|
|
66
|
+
{
|
|
67
|
+
status: 'success',
|
|
68
|
+
change_id: changeId,
|
|
69
|
+
...(options.message ? { message: options.message } : {}),
|
|
70
|
+
},
|
|
71
|
+
null,
|
|
72
|
+
2,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
} else if (options.xml) {
|
|
50
76
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
51
77
|
console.log(`<abandon_result>`)
|
|
52
78
|
console.log(` <status>success</status>`)
|
|
@@ -6,6 +6,7 @@ interface AddReviewerOptions {
|
|
|
6
6
|
cc?: boolean
|
|
7
7
|
notify?: string
|
|
8
8
|
xml?: boolean
|
|
9
|
+
json?: boolean
|
|
9
10
|
group?: boolean
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -24,6 +25,10 @@ const outputXmlError = (message: string): void => {
|
|
|
24
25
|
console.log(`</add_reviewer_result>`)
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
const outputJsonError = (message: string): void => {
|
|
29
|
+
console.log(JSON.stringify({ status: 'error', error: message }, null, 2))
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
class ValidationError extends Error {
|
|
28
33
|
readonly _tag = 'ValidationError'
|
|
29
34
|
}
|
|
@@ -40,7 +45,9 @@ export const addReviewerCommand = (
|
|
|
40
45
|
if (!changeId) {
|
|
41
46
|
const message =
|
|
42
47
|
'Change ID is required. Use -c <change-id> or run from a branch with an active change.'
|
|
43
|
-
if (options.
|
|
48
|
+
if (options.json) {
|
|
49
|
+
outputJsonError(message)
|
|
50
|
+
} else if (options.xml) {
|
|
44
51
|
outputXmlError(message)
|
|
45
52
|
} else {
|
|
46
53
|
console.error(`✗ ${message}`)
|
|
@@ -51,7 +58,9 @@ export const addReviewerCommand = (
|
|
|
51
58
|
if (reviewers.length === 0) {
|
|
52
59
|
const entityType = options.group ? 'group' : 'reviewer'
|
|
53
60
|
const message = `At least one ${entityType} is required.`
|
|
54
|
-
if (options.
|
|
61
|
+
if (options.json) {
|
|
62
|
+
outputJsonError(message)
|
|
63
|
+
} else if (options.xml) {
|
|
55
64
|
outputXmlError(message)
|
|
56
65
|
} else {
|
|
57
66
|
console.error(`✗ ${message}`)
|
|
@@ -67,7 +76,9 @@ export const addReviewerCommand = (
|
|
|
67
76
|
const emailLikeInputs = reviewers.filter((r) => r.includes('@'))
|
|
68
77
|
if (emailLikeInputs.length > 0) {
|
|
69
78
|
const message = `The --group flag expects group identifiers, but received email-like input: ${emailLikeInputs.join(', ')}. Did you mean to omit --group?`
|
|
70
|
-
if (options.
|
|
79
|
+
if (options.json) {
|
|
80
|
+
outputJsonError(message)
|
|
81
|
+
} else if (options.xml) {
|
|
71
82
|
outputXmlError(message)
|
|
72
83
|
} else {
|
|
73
84
|
console.error(`✗ ${message}`)
|
|
@@ -85,7 +96,9 @@ export const addReviewerCommand = (
|
|
|
85
96
|
const upperNotify = options.notify.toUpperCase()
|
|
86
97
|
if (!VALID_NOTIFY_LEVELS.includes(upperNotify as NotifyLevel)) {
|
|
87
98
|
const message = `Invalid notify level: ${options.notify}. Valid values: none, owner, owner_reviewers, all`
|
|
88
|
-
if (options.
|
|
99
|
+
if (options.json) {
|
|
100
|
+
outputJsonError(message)
|
|
101
|
+
} else if (options.xml) {
|
|
89
102
|
outputXmlError(message)
|
|
90
103
|
} else {
|
|
91
104
|
console.error(`✗ ${message}`)
|
|
@@ -120,7 +133,26 @@ export const addReviewerCommand = (
|
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
if (options.
|
|
136
|
+
if (options.json) {
|
|
137
|
+
const allSuccess = results.every((r) => r.success)
|
|
138
|
+
console.log(
|
|
139
|
+
JSON.stringify(
|
|
140
|
+
{
|
|
141
|
+
status: allSuccess ? 'success' : 'partial_failure',
|
|
142
|
+
change_id: changeId,
|
|
143
|
+
state,
|
|
144
|
+
entity_type: entityType,
|
|
145
|
+
reviewers: results.map((r) =>
|
|
146
|
+
r.success
|
|
147
|
+
? { input: r.reviewer, name: r.name, status: 'added' }
|
|
148
|
+
: { input: r.reviewer, error: r.error, status: 'failed' },
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
null,
|
|
152
|
+
2,
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
} else if (options.xml) {
|
|
124
156
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
125
157
|
console.log(`<add_reviewer_result>`)
|
|
126
158
|
console.log(` <change_id>${escapeXml(changeId)}</change_id>`)
|
|
@@ -14,18 +14,27 @@ Examples:
|
|
|
14
14
|
# Post a line-specific comment
|
|
15
15
|
$ ger comment 12345 --file src/main.js --line 42 -m "Consider using const here"
|
|
16
16
|
|
|
17
|
+
# Reply to a specific comment thread (resolves the thread by default)
|
|
18
|
+
$ ger comment 12345 --file src/main.js --line 42 --reply-to 37935b71_9e79a76c -m "Done, fixed"
|
|
19
|
+
|
|
20
|
+
# Reply but keep the thread unresolved
|
|
21
|
+
$ ger comment 12345 --file src/main.js --line 42 --reply-to 37935b71_9e79a76c --unresolved -m "What do you think?"
|
|
22
|
+
|
|
17
23
|
# Post multiple comments using batch mode
|
|
18
24
|
$ echo '{"message": "Review complete", "comments": [
|
|
19
25
|
{"file": "src/main.js", "line": 10, "message": "Good refactor"}
|
|
20
26
|
]}' | ger comment 12345 --batch
|
|
21
27
|
|
|
22
|
-
Note: Line numbers refer to the NEW version of the file, not diff line numbers
|
|
28
|
+
Note: Line numbers refer to the NEW version of the file, not diff line numbers.
|
|
29
|
+
Note: Comment IDs for --reply-to can be found in \`ger comments --xml\` or \`ger comments --json\` output (<id> / "id" field).`
|
|
23
30
|
|
|
24
31
|
interface CommentOptions {
|
|
25
32
|
message?: string
|
|
26
33
|
xml?: boolean
|
|
34
|
+
json?: boolean
|
|
27
35
|
file?: string
|
|
28
36
|
line?: number
|
|
37
|
+
replyTo?: string
|
|
29
38
|
unresolved?: boolean
|
|
30
39
|
batch?: boolean
|
|
31
40
|
}
|
|
@@ -185,6 +194,21 @@ export const createReviewInputFromString = (
|
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
const createReviewInput = (options: CommentOptions): Effect.Effect<ReviewInput, Error> => {
|
|
197
|
+
// Validate --reply-to constraints early
|
|
198
|
+
if (options.replyTo !== undefined) {
|
|
199
|
+
if (options.batch) {
|
|
200
|
+
return Effect.fail(new Error('--reply-to cannot be used with --batch'))
|
|
201
|
+
}
|
|
202
|
+
if (!(options.file && options.line)) {
|
|
203
|
+
return Effect.fail(new Error('--reply-to requires --file and --line'))
|
|
204
|
+
}
|
|
205
|
+
if (options.replyTo.trim().length === 0) {
|
|
206
|
+
return Effect.fail(new Error('--reply-to comment ID cannot be empty'))
|
|
207
|
+
}
|
|
208
|
+
// Normalize to trimmed value so the payload never contains leading/trailing whitespace
|
|
209
|
+
options = { ...options, replyTo: options.replyTo.trim() }
|
|
210
|
+
}
|
|
211
|
+
|
|
188
212
|
// Batch mode
|
|
189
213
|
if (options.batch) {
|
|
190
214
|
return pipe(
|
|
@@ -226,7 +250,13 @@ const createReviewInput = (options: CommentOptions): Effect.Effect<ReviewInput,
|
|
|
226
250
|
{
|
|
227
251
|
line: options.line,
|
|
228
252
|
message: options.message,
|
|
229
|
-
|
|
253
|
+
...(options.replyTo !== undefined ? { in_reply_to: options.replyTo } : {}),
|
|
254
|
+
// When replying, default unresolved to false (resolves the thread) unless explicitly set
|
|
255
|
+
...(options.replyTo !== undefined
|
|
256
|
+
? { unresolved: options.unresolved ?? false }
|
|
257
|
+
: options.unresolved !== undefined
|
|
258
|
+
? { unresolved: options.unresolved }
|
|
259
|
+
: {}),
|
|
230
260
|
},
|
|
231
261
|
],
|
|
232
262
|
},
|
|
@@ -428,8 +458,14 @@ const formatXmlOutput = (
|
|
|
428
458
|
lines.push(` <comment>`)
|
|
429
459
|
lines.push(` <file>${options.file}</file>`)
|
|
430
460
|
lines.push(` <line>${options.line}</line>`)
|
|
461
|
+
if (options.replyTo) lines.push(` <in_reply_to>${options.replyTo}</in_reply_to>`)
|
|
431
462
|
lines.push(` <message><![CDATA[${options.message}]]></message>`)
|
|
432
|
-
|
|
463
|
+
// Always emit unresolved when replying so callers know thread resolution state
|
|
464
|
+
if (options.replyTo !== undefined) {
|
|
465
|
+
lines.push(` <unresolved>${(options.unresolved ?? false).toString()}</unresolved>`)
|
|
466
|
+
} else if (options.unresolved) {
|
|
467
|
+
lines.push(` <unresolved>true</unresolved>`)
|
|
468
|
+
}
|
|
433
469
|
lines.push(` </comment>`)
|
|
434
470
|
} else {
|
|
435
471
|
lines.push(` <message><![CDATA[${options.message}]]></message>`)
|
|
@@ -459,6 +495,10 @@ const formatHumanOutput = (
|
|
|
459
495
|
console.log(`Posted ${totalComments} line comment(s)`)
|
|
460
496
|
} else if (options.file && options.line) {
|
|
461
497
|
console.log(`File: ${options.file}, Line: ${options.line}`)
|
|
498
|
+
if (options.replyTo) {
|
|
499
|
+
const resolved = !(options.unresolved ?? false)
|
|
500
|
+
console.log(`Reply to: ${options.replyTo} (thread ${resolved ? 'resolved' : 'unresolved'})`)
|
|
501
|
+
}
|
|
462
502
|
console.log(`Message: ${options.message}`)
|
|
463
503
|
if (options.unresolved) console.log(`Status: Unresolved`)
|
|
464
504
|
}
|
|
@@ -466,6 +506,51 @@ const formatHumanOutput = (
|
|
|
466
506
|
// since it was already shown in the "OVERALL REVIEW TO POST" section
|
|
467
507
|
})
|
|
468
508
|
|
|
509
|
+
// Helper to format JSON output
|
|
510
|
+
const formatJsonOutput = (
|
|
511
|
+
change: ChangeInfo,
|
|
512
|
+
review: ReviewInput,
|
|
513
|
+
options: CommentOptions,
|
|
514
|
+
changeId: string,
|
|
515
|
+
): Effect.Effect<void> =>
|
|
516
|
+
Effect.sync(() => {
|
|
517
|
+
const output: Record<string, unknown> = {
|
|
518
|
+
status: 'success',
|
|
519
|
+
change_id: changeId,
|
|
520
|
+
change_number: change._number,
|
|
521
|
+
change_subject: change.subject,
|
|
522
|
+
change_status: change.status,
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (options.batch && review.comments) {
|
|
526
|
+
output.comments = Object.entries(review.comments).flatMap(([file, comments]) =>
|
|
527
|
+
comments.map((comment) => ({
|
|
528
|
+
file,
|
|
529
|
+
...(comment.line ? { line: comment.line } : {}),
|
|
530
|
+
message: comment.message,
|
|
531
|
+
...(comment.unresolved ? { unresolved: true } : {}),
|
|
532
|
+
})),
|
|
533
|
+
)
|
|
534
|
+
} else if (options.file && options.line) {
|
|
535
|
+
output.comment = {
|
|
536
|
+
file: options.file,
|
|
537
|
+
line: options.line,
|
|
538
|
+
...(options.replyTo ? { in_reply_to: options.replyTo } : {}),
|
|
539
|
+
message: options.message,
|
|
540
|
+
// Always include unresolved when replying so callers know thread resolution state
|
|
541
|
+
...(options.replyTo !== undefined
|
|
542
|
+
? { unresolved: options.unresolved ?? false }
|
|
543
|
+
: options.unresolved
|
|
544
|
+
? { unresolved: true }
|
|
545
|
+
: {}),
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
output.message = options.message
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
console.log(JSON.stringify(output, null, 2))
|
|
552
|
+
})
|
|
553
|
+
|
|
469
554
|
// Main output formatter
|
|
470
555
|
const formatOutput = (
|
|
471
556
|
change: ChangeInfo,
|
|
@@ -473,6 +558,8 @@ const formatOutput = (
|
|
|
473
558
|
options: CommentOptions,
|
|
474
559
|
changeId: string,
|
|
475
560
|
): Effect.Effect<void> =>
|
|
476
|
-
options.
|
|
477
|
-
?
|
|
478
|
-
:
|
|
561
|
+
options.json
|
|
562
|
+
? formatJsonOutput(change, review, options, changeId)
|
|
563
|
+
: options.xml
|
|
564
|
+
? formatXmlOutput(change, review, options, changeId)
|
|
565
|
+
: formatHumanOutput(change, review, options)
|
|
@@ -6,6 +6,7 @@ import { getDiffContext } from '@/utils/diff-context'
|
|
|
6
6
|
|
|
7
7
|
interface CommentsOptions {
|
|
8
8
|
xml?: boolean
|
|
9
|
+
json?: boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const getCommentsForChange = (
|
|
@@ -63,7 +64,36 @@ export const commentsCommand = (
|
|
|
63
64
|
})
|
|
64
65
|
|
|
65
66
|
// Format output
|
|
66
|
-
if (options.
|
|
67
|
+
if (options.json) {
|
|
68
|
+
const jsonOutput = {
|
|
69
|
+
status: 'success',
|
|
70
|
+
change_id: changeId,
|
|
71
|
+
comment_count: commentsWithContext.length,
|
|
72
|
+
comments: commentsWithContext.map(({ comment, context }) => ({
|
|
73
|
+
id: comment.id,
|
|
74
|
+
...(comment.path ? { path: comment.path } : {}),
|
|
75
|
+
...(comment.line ? { line: comment.line } : {}),
|
|
76
|
+
...(comment.range ? { range: comment.range } : {}),
|
|
77
|
+
...(comment.author
|
|
78
|
+
? {
|
|
79
|
+
author: {
|
|
80
|
+
...(comment.author.name ? { name: comment.author.name } : {}),
|
|
81
|
+
...(comment.author.email ? { email: comment.author.email } : {}),
|
|
82
|
+
...(comment.author._account_id !== undefined
|
|
83
|
+
? { account_id: comment.author._account_id }
|
|
84
|
+
: {}),
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
: {}),
|
|
88
|
+
...(comment.updated ? { updated: comment.updated } : {}),
|
|
89
|
+
...(comment.unresolved !== undefined ? { unresolved: comment.unresolved } : {}),
|
|
90
|
+
...(comment.in_reply_to ? { in_reply_to: comment.in_reply_to } : {}),
|
|
91
|
+
message: comment.message,
|
|
92
|
+
...(context ? { diff_context: context } : {}),
|
|
93
|
+
})),
|
|
94
|
+
}
|
|
95
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
96
|
+
} else if (options.xml) {
|
|
67
97
|
formatCommentsXml(changeId, commentsWithContext)
|
|
68
98
|
} else {
|
|
69
99
|
formatCommentsPretty(commentsWithContext)
|
|
@@ -71,7 +101,9 @@ export const commentsCommand = (
|
|
|
71
101
|
}).pipe(
|
|
72
102
|
// Regional error boundary for the entire command
|
|
73
103
|
Effect.catchTag('ApiError', (error) => {
|
|
74
|
-
if (options.
|
|
104
|
+
if (options.json) {
|
|
105
|
+
console.log(JSON.stringify({ status: 'error', error: error.message }, null, 2))
|
|
106
|
+
} else if (options.xml) {
|
|
75
107
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
76
108
|
console.log(`<comments_result>`)
|
|
77
109
|
console.log(` <status>error</status>`)
|
package/src/cli/commands/diff.ts
CHANGED
|
@@ -35,7 +35,19 @@ export const diffCommand = (
|
|
|
35
35
|
),
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
if (validatedOptions.
|
|
38
|
+
if (validatedOptions.json) {
|
|
39
|
+
// JSON output
|
|
40
|
+
const jsonOutput: Record<string, unknown> = {
|
|
41
|
+
status: 'success',
|
|
42
|
+
change_id: changeId,
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(diff)) {
|
|
45
|
+
jsonOutput.files = diff
|
|
46
|
+
} else {
|
|
47
|
+
jsonOutput.content = diff
|
|
48
|
+
}
|
|
49
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
50
|
+
} else if (validatedOptions.xml) {
|
|
39
51
|
// XML output for LLM consumption
|
|
40
52
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
41
53
|
console.log(`<diff_result>`)
|
|
@@ -4,6 +4,7 @@ import { sanitizeCDATA } from '@/utils/shell-safety'
|
|
|
4
4
|
|
|
5
5
|
interface GroupsMembersOptions {
|
|
6
6
|
xml?: boolean
|
|
7
|
+
json?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -25,7 +26,9 @@ export const groupsMembersCommand = (
|
|
|
25
26
|
const members = yield* gerritApi.getGroupMembers(groupId).pipe(
|
|
26
27
|
Effect.catchTag('ApiError', (error) =>
|
|
27
28
|
Effect.gen(function* () {
|
|
28
|
-
if (options.
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(JSON.stringify({ status: 'error', error: error.message }, null, 2))
|
|
31
|
+
} else if (options.xml) {
|
|
29
32
|
console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
30
33
|
console.log('<group_members_result>')
|
|
31
34
|
console.log(' <status>error</status>')
|
|
@@ -47,7 +50,11 @@ export const groupsMembersCommand = (
|
|
|
47
50
|
|
|
48
51
|
// Handle empty results
|
|
49
52
|
if (members.length === 0) {
|
|
50
|
-
if (options.
|
|
53
|
+
if (options.json) {
|
|
54
|
+
console.log(
|
|
55
|
+
JSON.stringify({ status: 'success', group_id: groupId, count: 0, members: [] }, null, 2),
|
|
56
|
+
)
|
|
57
|
+
} else if (options.xml) {
|
|
51
58
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
52
59
|
console.log(`<group_members_result>`)
|
|
53
60
|
console.log(` <status>success</status>`)
|
|
@@ -62,7 +69,25 @@ export const groupsMembersCommand = (
|
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
// Output results
|
|
65
|
-
if (options.
|
|
72
|
+
if (options.json) {
|
|
73
|
+
console.log(
|
|
74
|
+
JSON.stringify(
|
|
75
|
+
{
|
|
76
|
+
status: 'success',
|
|
77
|
+
group_id: groupId,
|
|
78
|
+
count: members.length,
|
|
79
|
+
members: members.map((member) => ({
|
|
80
|
+
account_id: member._account_id,
|
|
81
|
+
...(member.name ? { name: member.name } : {}),
|
|
82
|
+
...(member.email ? { email: member.email } : {}),
|
|
83
|
+
...(member.username ? { username: member.username } : {}),
|
|
84
|
+
})),
|
|
85
|
+
},
|
|
86
|
+
null,
|
|
87
|
+
2,
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
} else if (options.xml) {
|
|
66
91
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
67
92
|
console.log(`<group_members_result>`)
|
|
68
93
|
console.log(` <status>success</status>`)
|
|
@@ -4,6 +4,7 @@ import { sanitizeCDATA } from '@/utils/shell-safety'
|
|
|
4
4
|
|
|
5
5
|
interface GroupsShowOptions {
|
|
6
6
|
xml?: boolean
|
|
7
|
+
json?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -25,7 +26,9 @@ export const groupsShowCommand = (
|
|
|
25
26
|
const group = yield* gerritApi.getGroupDetail(groupId).pipe(
|
|
26
27
|
Effect.catchTag('ApiError', (error) =>
|
|
27
28
|
Effect.gen(function* () {
|
|
28
|
-
if (options.
|
|
29
|
+
if (options.json) {
|
|
30
|
+
console.log(JSON.stringify({ status: 'error', error: error.message }, null, 2))
|
|
31
|
+
} else if (options.xml) {
|
|
29
32
|
console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
30
33
|
console.log('<group_detail_result>')
|
|
31
34
|
console.log(' <status>error</status>')
|
|
@@ -46,7 +49,40 @@ export const groupsShowCommand = (
|
|
|
46
49
|
)
|
|
47
50
|
|
|
48
51
|
// Output results
|
|
49
|
-
if (options.
|
|
52
|
+
if (options.json) {
|
|
53
|
+
console.log(
|
|
54
|
+
JSON.stringify(
|
|
55
|
+
{
|
|
56
|
+
status: 'success',
|
|
57
|
+
group: {
|
|
58
|
+
id: group.id,
|
|
59
|
+
...(group.name ? { name: group.name } : {}),
|
|
60
|
+
...(group.description ? { description: group.description } : {}),
|
|
61
|
+
...(group.owner ? { owner: group.owner } : {}),
|
|
62
|
+
...(group.owner_id ? { owner_id: group.owner_id } : {}),
|
|
63
|
+
...(group.group_id !== undefined ? { group_id: group.group_id } : {}),
|
|
64
|
+
...(group.options?.visible_to_all !== undefined
|
|
65
|
+
? { visible_to_all: group.options.visible_to_all }
|
|
66
|
+
: {}),
|
|
67
|
+
...(group.created_on ? { created_on: group.created_on } : {}),
|
|
68
|
+
...(group.url ? { url: group.url } : {}),
|
|
69
|
+
members: (group.members ?? []).map((member) => ({
|
|
70
|
+
account_id: member._account_id,
|
|
71
|
+
...(member.name ? { name: member.name } : {}),
|
|
72
|
+
...(member.email ? { email: member.email } : {}),
|
|
73
|
+
...(member.username ? { username: member.username } : {}),
|
|
74
|
+
})),
|
|
75
|
+
subgroups: (group.includes ?? []).map((subgroup) => ({
|
|
76
|
+
id: subgroup.id,
|
|
77
|
+
...(subgroup.name ? { name: subgroup.name } : {}),
|
|
78
|
+
})),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
null,
|
|
82
|
+
2,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
} else if (options.xml) {
|
|
50
86
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
51
87
|
console.log(`<group_detail_result>`)
|
|
52
88
|
console.log(` <status>success</status>`)
|
|
@@ -9,6 +9,7 @@ interface GroupsOptions {
|
|
|
9
9
|
user?: string
|
|
10
10
|
limit?: string
|
|
11
11
|
xml?: boolean
|
|
12
|
+
json?: boolean
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -44,7 +45,9 @@ export const groupsCommand = (
|
|
|
44
45
|
.pipe(
|
|
45
46
|
Effect.catchTag('ApiError', (error) =>
|
|
46
47
|
Effect.gen(function* () {
|
|
47
|
-
if (options.
|
|
48
|
+
if (options.json) {
|
|
49
|
+
console.log(JSON.stringify({ status: 'error', error: error.message }, null, 2))
|
|
50
|
+
} else if (options.xml) {
|
|
48
51
|
console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
49
52
|
console.log('<groups_result>')
|
|
50
53
|
console.log(' <status>error</status>')
|
|
@@ -64,7 +67,9 @@ export const groupsCommand = (
|
|
|
64
67
|
|
|
65
68
|
// Handle empty results
|
|
66
69
|
if (groups.length === 0) {
|
|
67
|
-
if (options.
|
|
70
|
+
if (options.json) {
|
|
71
|
+
console.log(JSON.stringify({ status: 'success', count: 0, groups: [] }, null, 2))
|
|
72
|
+
} else if (options.xml) {
|
|
68
73
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
69
74
|
console.log(`<groups_result>`)
|
|
70
75
|
console.log(` <status>success</status>`)
|
|
@@ -78,7 +83,31 @@ export const groupsCommand = (
|
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
// Output results
|
|
81
|
-
if (options.
|
|
86
|
+
if (options.json) {
|
|
87
|
+
console.log(
|
|
88
|
+
JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
status: 'success',
|
|
91
|
+
count: groups.length,
|
|
92
|
+
groups: groups.map((group) => ({
|
|
93
|
+
id: group.id,
|
|
94
|
+
...(group.name ? { name: group.name } : {}),
|
|
95
|
+
...(group.description ? { description: group.description } : {}),
|
|
96
|
+
...(group.owner ? { owner: group.owner } : {}),
|
|
97
|
+
...(group.owner_id ? { owner_id: group.owner_id } : {}),
|
|
98
|
+
...(group.group_id !== undefined ? { group_id: group.group_id } : {}),
|
|
99
|
+
...(group.options?.visible_to_all !== undefined
|
|
100
|
+
? { visible_to_all: group.options.visible_to_all }
|
|
101
|
+
: {}),
|
|
102
|
+
...(group.created_on ? { created_on: group.created_on } : {}),
|
|
103
|
+
...(group.url ? { url: group.url } : {}),
|
|
104
|
+
})),
|
|
105
|
+
},
|
|
106
|
+
null,
|
|
107
|
+
2,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
} else if (options.xml) {
|
|
82
111
|
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
83
112
|
console.log(`<groups_result>`)
|
|
84
113
|
console.log(` <status>success</status>`)
|
|
@@ -13,6 +13,7 @@ const execAsync = promisify(exec)
|
|
|
13
13
|
|
|
14
14
|
interface IncomingOptions {
|
|
15
15
|
xml?: boolean
|
|
16
|
+
json?: boolean
|
|
16
17
|
interactive?: boolean
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -161,7 +162,26 @@ export const incomingCommand = (
|
|
|
161
162
|
return
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
if (options.
|
|
165
|
+
if (options.json) {
|
|
166
|
+
// JSON output
|
|
167
|
+
const groupedChanges = groupChangesByProject(changes)
|
|
168
|
+
const jsonOutput = {
|
|
169
|
+
status: 'success',
|
|
170
|
+
count: changes.length,
|
|
171
|
+
changes: groupedChanges.flatMap(({ project, changes: projectChanges }) =>
|
|
172
|
+
projectChanges.map((change) => ({
|
|
173
|
+
number: change._number,
|
|
174
|
+
subject: change.subject,
|
|
175
|
+
status: change.status,
|
|
176
|
+
project,
|
|
177
|
+
owner: change.owner?.name ?? 'Unknown',
|
|
178
|
+
...(change.owner?.email ? { owner_email: change.owner.email } : {}),
|
|
179
|
+
...(change.updated ? { updated: change.updated } : {}),
|
|
180
|
+
})),
|
|
181
|
+
),
|
|
182
|
+
}
|
|
183
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
184
|
+
} else if (options.xml) {
|
|
165
185
|
// XML output
|
|
166
186
|
const xmlOutput = [
|
|
167
187
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
@@ -11,6 +11,7 @@ import { type ConfigError, type ConfigServiceImpl } from '@/services/config'
|
|
|
11
11
|
export interface InstallHookOptions {
|
|
12
12
|
force?: boolean
|
|
13
13
|
xml?: boolean
|
|
14
|
+
json?: boolean
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export type InstallHookErrors = ConfigError | HookInstallError | NotGitRepoError
|
|
@@ -25,7 +26,19 @@ export const installHookCommand = (
|
|
|
25
26
|
const hookExists = yield* commitHookService.hasHook()
|
|
26
27
|
|
|
27
28
|
if (hookExists && !options.force) {
|
|
28
|
-
if (options.
|
|
29
|
+
if (options.json) {
|
|
30
|
+
yield* Console.log(
|
|
31
|
+
JSON.stringify(
|
|
32
|
+
{
|
|
33
|
+
status: 'skipped',
|
|
34
|
+
message: 'commit-msg hook already installed',
|
|
35
|
+
hint: 'Use --force to overwrite',
|
|
36
|
+
},
|
|
37
|
+
null,
|
|
38
|
+
2,
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
} else if (options.xml) {
|
|
29
42
|
yield* Console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
30
43
|
yield* Console.log('<install_hook_result>')
|
|
31
44
|
yield* Console.log(' <status>skipped</status>')
|
|
@@ -40,16 +53,24 @@ export const installHookCommand = (
|
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
if (hookExists && options.force) {
|
|
43
|
-
if (!options.xml) {
|
|
56
|
+
if (!options.xml && !options.json) {
|
|
44
57
|
yield* Console.log(chalk.yellow('Overwriting existing commit-msg hook...'))
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
|
|
49
|
-
yield* commitHookService.installHook()
|
|
61
|
+
const quiet = options.xml === true || options.json === true
|
|
62
|
+
yield* commitHookService.installHook(quiet)
|
|
50
63
|
|
|
51
|
-
// Only output XML here - service already logs success message for
|
|
52
|
-
if (options.
|
|
64
|
+
// Only output JSON/XML here - service already logs success message for plain mode
|
|
65
|
+
if (options.json) {
|
|
66
|
+
yield* Console.log(
|
|
67
|
+
JSON.stringify(
|
|
68
|
+
{ status: 'success', message: 'commit-msg hook installed successfully' },
|
|
69
|
+
null,
|
|
70
|
+
2,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
} else if (options.xml) {
|
|
53
74
|
yield* Console.log('<?xml version="1.0" encoding="UTF-8"?>')
|
|
54
75
|
yield* Console.log('<install_hook_result>')
|
|
55
76
|
yield* Console.log(' <status>success</status>')
|