@aaronshaf/ger 0.1.6 → 0.1.8
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/comment.ts +2 -2
- package/src/cli/commands/setup.ts +110 -55
- package/src/cli/index.ts +21 -1
- package/tests/comment.test.ts +2 -1
package/package.json
CHANGED
|
@@ -443,9 +443,9 @@ const formatHumanOutput = (
|
|
|
443
443
|
console.log(`File: ${options.file}, Line: ${options.line}`)
|
|
444
444
|
console.log(`Message: ${options.message}`)
|
|
445
445
|
if (options.unresolved) console.log(`Status: Unresolved`)
|
|
446
|
-
} else {
|
|
447
|
-
console.log(`Message: ${review.message}`)
|
|
448
446
|
}
|
|
447
|
+
// Note: For overall review messages, we don't display the content here
|
|
448
|
+
// since it was already shown in the "OVERALL REVIEW TO POST" section
|
|
449
449
|
})
|
|
450
450
|
|
|
451
451
|
// Main output formatter
|
|
@@ -66,14 +66,54 @@ const verifyCredentials = (credentials: GerritCredentials) =>
|
|
|
66
66
|
},
|
|
67
67
|
catch: (error) => {
|
|
68
68
|
if (error instanceof Error) {
|
|
69
|
+
// Authentication/permission errors
|
|
69
70
|
if (error.message.includes('401')) {
|
|
70
71
|
return new ConfigError({
|
|
71
72
|
message: 'Invalid credentials. Please check your username and password.',
|
|
72
73
|
})
|
|
73
74
|
}
|
|
75
|
+
if (error.message.includes('403')) {
|
|
76
|
+
return new ConfigError({
|
|
77
|
+
message: 'Access denied. Please verify your credentials and server permissions.',
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Network/hostname errors
|
|
74
82
|
if (error.message.includes('ENOTFOUND')) {
|
|
75
|
-
return new ConfigError({
|
|
83
|
+
return new ConfigError({
|
|
84
|
+
message: `Hostname not found. Please check that the Gerrit URL is correct.\nExample: https://gerrit.example.com (without /a/ or paths)`,
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
if (error.message.includes('ECONNREFUSED')) {
|
|
88
|
+
return new ConfigError({
|
|
89
|
+
message: `Connection refused. The server may be down or the port may be incorrect.\nPlease verify the URL and try again.`,
|
|
90
|
+
})
|
|
76
91
|
}
|
|
92
|
+
if (error.message.includes('ETIMEDOUT')) {
|
|
93
|
+
return new ConfigError({
|
|
94
|
+
message: `Connection timed out. Please check:\n• Your internet connection\n• The Gerrit server URL\n• Any firewall or VPN settings`,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
if (error.message.includes('certificate') || error.message.includes('SSL')) {
|
|
98
|
+
return new ConfigError({
|
|
99
|
+
message: `SSL/Certificate error. Please ensure the URL uses HTTPS and the certificate is valid.`,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// URL format errors
|
|
104
|
+
if (error.message.includes('Invalid URL') || error.message.includes('fetch failed')) {
|
|
105
|
+
return new ConfigError({
|
|
106
|
+
message: `Invalid URL format. Please use the full URL including https://\nExample: https://gerrit.example.com`,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Generic network errors
|
|
111
|
+
if (error.message.includes('network') || error.message.includes('fetch')) {
|
|
112
|
+
return new ConfigError({
|
|
113
|
+
message: `Network error: ${error.message}\nPlease check your connection and the Gerrit server URL.`,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
77
117
|
return new ConfigError({ message: error.message })
|
|
78
118
|
}
|
|
79
119
|
return new ConfigError({ message: 'Unknown error occurred' })
|
|
@@ -112,64 +152,82 @@ const setupEffect = (configService: ConfigServiceImpl) =>
|
|
|
112
152
|
try: async () => {
|
|
113
153
|
console.log('')
|
|
114
154
|
|
|
115
|
-
//
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
155
|
+
// Enable raw mode for proper password masking
|
|
156
|
+
const wasRawMode = process.stdin.isRaw
|
|
157
|
+
if (process.stdin.isTTY && !wasRawMode) {
|
|
158
|
+
process.stdin.setRawMode(true)
|
|
159
|
+
}
|
|
120
160
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
try {
|
|
162
|
+
// Gerrit Host URL
|
|
163
|
+
const host = await input({
|
|
164
|
+
message: 'Gerrit Host URL (e.g., https://gerrit.example.com)',
|
|
165
|
+
default: existingConfig?.host,
|
|
166
|
+
})
|
|
126
167
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
})
|
|
132
|
-
existingConfig?.password ||
|
|
133
|
-
''
|
|
168
|
+
// Username
|
|
169
|
+
const username = await input({
|
|
170
|
+
message: 'Username (your Gerrit username)',
|
|
171
|
+
default: existingConfig?.username,
|
|
172
|
+
})
|
|
134
173
|
|
|
135
|
-
|
|
136
|
-
|
|
174
|
+
// Password - with proper masking and visual feedback
|
|
175
|
+
const passwordMessage = existingConfig?.password
|
|
176
|
+
? `HTTP Password (generated from Gerrit settings) ${chalk.dim('(press Enter to keep existing)')}`
|
|
177
|
+
: 'HTTP Password (generated from Gerrit settings)'
|
|
137
178
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
179
|
+
const passwordValue =
|
|
180
|
+
(await password({
|
|
181
|
+
message: passwordMessage,
|
|
182
|
+
mask: true, // Show * characters as user types
|
|
183
|
+
})) ||
|
|
184
|
+
existingConfig?.password ||
|
|
185
|
+
''
|
|
142
186
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
existingConfig?.aiTool ||
|
|
146
|
-
(availableTools.includes('claude') ? 'claude' : availableTools[0]) ||
|
|
147
|
-
''
|
|
148
|
-
|
|
149
|
-
// AI tool command with smart default
|
|
150
|
-
const aiToolCommand = await input({
|
|
151
|
-
message:
|
|
152
|
-
availableTools.length > 0
|
|
153
|
-
? 'AI tool command (detected from system)'
|
|
154
|
-
: 'AI tool command (e.g., claude, llm, opencode, gemini)',
|
|
155
|
-
default: defaultCommand || 'claude',
|
|
156
|
-
})
|
|
187
|
+
console.log('')
|
|
188
|
+
console.log(chalk.yellow('Optional: AI Configuration'))
|
|
157
189
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
password: passwordValue,
|
|
163
|
-
...(aiToolCommand && {
|
|
164
|
-
aiTool: aiToolCommand,
|
|
165
|
-
}),
|
|
166
|
-
aiAutoDetect: !aiToolCommand,
|
|
167
|
-
}
|
|
190
|
+
// Show detected AI tools
|
|
191
|
+
if (availableTools.length > 0) {
|
|
192
|
+
console.log(chalk.dim(`Detected AI tools: ${availableTools.join(', ')}`))
|
|
193
|
+
}
|
|
168
194
|
|
|
169
|
-
|
|
170
|
-
|
|
195
|
+
// Get default suggestion
|
|
196
|
+
const defaultCommand =
|
|
197
|
+
existingConfig?.aiTool ||
|
|
198
|
+
(availableTools.includes('claude') ? 'claude' : availableTools[0]) ||
|
|
199
|
+
''
|
|
171
200
|
|
|
172
|
-
|
|
201
|
+
// AI tool command with smart default
|
|
202
|
+
const aiToolCommand = await input({
|
|
203
|
+
message:
|
|
204
|
+
availableTools.length > 0
|
|
205
|
+
? 'AI tool command (detected from system)'
|
|
206
|
+
: 'AI tool command (e.g., claude, llm, opencode, gemini)',
|
|
207
|
+
default: defaultCommand || 'claude',
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// Build flat config
|
|
211
|
+
const configData = {
|
|
212
|
+
host: host.trim().replace(/\/$/, ''), // Remove trailing slash
|
|
213
|
+
username: username.trim(),
|
|
214
|
+
password: passwordValue,
|
|
215
|
+
...(aiToolCommand && {
|
|
216
|
+
aiTool: aiToolCommand,
|
|
217
|
+
}),
|
|
218
|
+
aiAutoDetect: !aiToolCommand,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Validate config using Schema instead of type assertion
|
|
222
|
+
const fullConfig = Schema.decodeUnknownSync(AppConfig)(configData)
|
|
223
|
+
|
|
224
|
+
return fullConfig
|
|
225
|
+
} finally {
|
|
226
|
+
// Restore raw mode state
|
|
227
|
+
if (process.stdin.isTTY && !wasRawMode) {
|
|
228
|
+
process.stdin.setRawMode(false)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
173
231
|
},
|
|
174
232
|
catch: (error) => {
|
|
175
233
|
if (error instanceof Error && error.message.includes('User force closed')) {
|
|
@@ -205,11 +263,8 @@ const setupEffect = (configService: ConfigServiceImpl) =>
|
|
|
205
263
|
Effect.catchAll((error) =>
|
|
206
264
|
pipe(
|
|
207
265
|
Console.error(
|
|
208
|
-
chalk.red(
|
|
209
|
-
`\nAuthentication failed: ${error instanceof ConfigError ? error.message : 'Unknown error'}`,
|
|
210
|
-
),
|
|
266
|
+
chalk.red(`\n${error instanceof ConfigError ? error.message : `Setup failed: ${error}`}`),
|
|
211
267
|
),
|
|
212
|
-
Effect.flatMap(() => Console.error('Please check your credentials and try again.')),
|
|
213
268
|
Effect.flatMap(() => Effect.fail(error)),
|
|
214
269
|
),
|
|
215
270
|
),
|
package/src/cli/index.ts
CHANGED
|
@@ -23,6 +23,9 @@ if (compareSemver(bunVersion, MIN_BUN_VERSION) < 0) {
|
|
|
23
23
|
|
|
24
24
|
import { Command } from 'commander'
|
|
25
25
|
import { Effect } from 'effect'
|
|
26
|
+
import { readFileSync } from 'node:fs'
|
|
27
|
+
import { join, dirname } from 'node:path'
|
|
28
|
+
import { fileURLToPath } from 'node:url'
|
|
26
29
|
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
27
30
|
import { ConfigServiceLive } from '@/services/config'
|
|
28
31
|
import { ReviewStrategyServiceLive } from '@/services/review-strategy'
|
|
@@ -40,9 +43,26 @@ import { showCommand } from './commands/show'
|
|
|
40
43
|
import { statusCommand } from './commands/status'
|
|
41
44
|
import { workspaceCommand } from './commands/workspace'
|
|
42
45
|
|
|
46
|
+
// Read version from package.json
|
|
47
|
+
function getVersion(): string {
|
|
48
|
+
try {
|
|
49
|
+
// Get the directory of the current module
|
|
50
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
51
|
+
const __dirname = dirname(__filename)
|
|
52
|
+
|
|
53
|
+
// Navigate up to the project root and read package.json
|
|
54
|
+
const packageJsonPath = join(__dirname, '..', '..', 'package.json')
|
|
55
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
56
|
+
return packageJson.version || '0.0.0'
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Fallback version if package.json can't be read
|
|
59
|
+
return '0.0.0'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
43
63
|
const program = new Command()
|
|
44
64
|
|
|
45
|
-
program.name('gi').description('LLM-centric Gerrit CLI tool').version(
|
|
65
|
+
program.name('gi').description('LLM-centric Gerrit CLI tool').version(getVersion())
|
|
46
66
|
|
|
47
67
|
// setup command (new primary command)
|
|
48
68
|
program
|
package/tests/comment.test.ts
CHANGED
|
@@ -556,7 +556,8 @@ describe('comment command', () => {
|
|
|
556
556
|
|
|
557
557
|
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
558
558
|
expect(output).toContain('✓ Comment posted successfully!')
|
|
559
|
-
|
|
559
|
+
// Note: The message content is no longer displayed after successful posting
|
|
560
|
+
// to avoid duplication with the review preview section
|
|
560
561
|
|
|
561
562
|
// Restore process.stdin
|
|
562
563
|
Object.defineProperty(process, 'stdin', {
|