@aaronshaf/ger 0.2.4 → 0.3.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/CLAUDE.md +3 -3
- package/README.md +87 -0
- package/package.json +1 -1
- package/src/cli/commands/build-status.ts +238 -0
- package/src/cli/index.ts +80 -0
- package/tests/abandon.test.ts +178 -111
- package/tests/build-status-watch.test.ts +347 -0
- package/tests/build-status.test.ts +640 -0
- package/tests/helpers/build-status-test-setup.ts +83 -0
- package/tests/mine.test.ts +130 -163
- package/tests/mocks/fetch-mock.ts +0 -142
- package/tests/setup.ts +0 -13
package/CLAUDE.md
CHANGED
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
### Testing & Coverage
|
|
26
26
|
- **ENFORCE** minimum 80% code coverage
|
|
27
27
|
- **RUN** all tests in pre-commit and pre-push hooks
|
|
28
|
-
- **USE**
|
|
28
|
+
- **USE** MSW (Mock Service Worker) for all HTTP request mocking
|
|
29
29
|
- **REQUIRE** meaningful integration tests for all commands that simulate full workflows
|
|
30
30
|
- **IMPLEMENT** both unit tests and integration tests for every command modification/addition
|
|
31
|
-
- **ENSURE** integration tests use realistic
|
|
31
|
+
- **ENSURE** integration tests use realistic MSW handlers that match Gerrit API responses
|
|
32
32
|
- **EXCLUDE** generated code and tmp/ from coverage
|
|
33
33
|
|
|
34
34
|
### Security
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
### Testing Requirements for Commands
|
|
57
57
|
- **UNIT TESTS**: Test individual functions, schemas, and utilities
|
|
58
58
|
- **INTEGRATION TESTS**: Test complete command flows with mocked HTTP requests
|
|
59
|
-
- **HTTP MOCKING**: Use
|
|
59
|
+
- **HTTP MOCKING**: Use MSW handlers with http.get/http.post patterns for mocking
|
|
60
60
|
- **SCHEMA VALIDATION**: Ensure mocks return data that validates against Effect Schemas
|
|
61
61
|
- **COMMAND COVERAGE**: Every command must have integration tests covering:
|
|
62
62
|
- Happy path execution
|
package/README.md
CHANGED
|
@@ -55,6 +55,14 @@ ger diff 12345
|
|
|
55
55
|
# Extract URLs from messages (e.g., Jenkins build links)
|
|
56
56
|
ger extract-url "build-summary-report" | tail -1
|
|
57
57
|
|
|
58
|
+
# Check CI build status (parses build messages)
|
|
59
|
+
ger build-status 12345 # Returns: pending, running, success, failure, or not_found
|
|
60
|
+
ger build-status # Auto-detects from HEAD commit
|
|
61
|
+
|
|
62
|
+
# Watch build status until completion (like gh run watch)
|
|
63
|
+
ger build-status 12345 --watch
|
|
64
|
+
ger build-status --watch --exit-status && deploy.sh
|
|
65
|
+
|
|
58
66
|
# AI-powered code review (requires claude, llm, or opencode CLI)
|
|
59
67
|
ger review 12345
|
|
60
68
|
ger review 12345 --dry-run # Preview without posting
|
|
@@ -249,6 +257,85 @@ ger extract-url "github.com" --include-comments
|
|
|
249
257
|
ger extract-url "job/[^/]+/job/[^/]+/\d+/$" --regex
|
|
250
258
|
```
|
|
251
259
|
|
|
260
|
+
### Build Status
|
|
261
|
+
|
|
262
|
+
Check the CI build status of a change by parsing Gerrit messages for build events and verification results:
|
|
263
|
+
|
|
264
|
+
#### Single Check (Snapshot)
|
|
265
|
+
```bash
|
|
266
|
+
# Check build status for a specific change
|
|
267
|
+
ger build-status 12345
|
|
268
|
+
# Output: {"state":"success"}
|
|
269
|
+
|
|
270
|
+
# Auto-detect change from HEAD commit
|
|
271
|
+
ger build-status
|
|
272
|
+
|
|
273
|
+
# Use in scripts with jq
|
|
274
|
+
ger build-status | jq -r '.state'
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Watch Mode (Poll Until Completion)
|
|
278
|
+
Like `gh run watch`, you can poll the build status until it reaches a terminal state:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Watch until completion (outputs JSON on each poll)
|
|
282
|
+
ger build-status 12345 --watch
|
|
283
|
+
# Output:
|
|
284
|
+
# {"state":"pending"}
|
|
285
|
+
# {"state":"running"}
|
|
286
|
+
# {"state":"running"}
|
|
287
|
+
# {"state":"success"}
|
|
288
|
+
|
|
289
|
+
# Auto-detect from HEAD commit
|
|
290
|
+
ger build-status --watch
|
|
291
|
+
|
|
292
|
+
# Custom polling interval (check every 5 seconds, default: 10)
|
|
293
|
+
ger build-status --watch --interval 5
|
|
294
|
+
|
|
295
|
+
# Custom timeout (60 minutes, default: 30 minutes)
|
|
296
|
+
ger build-status --watch --timeout 3600
|
|
297
|
+
|
|
298
|
+
# Exit with code 1 on build failure (for CI/CD pipelines)
|
|
299
|
+
ger build-status --watch --exit-status && deploy.sh
|
|
300
|
+
|
|
301
|
+
# Trigger notification when done (like gh run watch pattern)
|
|
302
|
+
ger build-status --watch && notify-send 'Build is done!'
|
|
303
|
+
|
|
304
|
+
# Extract final state in scripts
|
|
305
|
+
ger build-status --watch | tail -1 | jq -r '.state'
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### Output format (JSON):
|
|
309
|
+
```json
|
|
310
|
+
{"state": "success"}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Build states:
|
|
314
|
+
- **`pending`**: No "Build Started" message found yet
|
|
315
|
+
- **`running`**: "Build Started" found, but no verification result yet
|
|
316
|
+
- **`success`**: Verified +1 after most recent "Build Started"
|
|
317
|
+
- **`failure`**: Verified -1 after most recent "Build Started"
|
|
318
|
+
- **`not_found`**: Change does not exist
|
|
319
|
+
|
|
320
|
+
#### Exit codes:
|
|
321
|
+
- **`0`**: Default for all states (like `gh run watch`)
|
|
322
|
+
- **`1`**: Only when `--exit-status` flag is used AND build fails
|
|
323
|
+
- **`2`**: Timeout reached in watch mode
|
|
324
|
+
- **`3`**: API/network errors
|
|
325
|
+
|
|
326
|
+
#### How it works:
|
|
327
|
+
1. Fetches all messages for the change
|
|
328
|
+
2. Finds the most recent "Build Started" message
|
|
329
|
+
3. Checks for "Verified +1" or "Verified -1" messages after the build started
|
|
330
|
+
4. Returns the appropriate state
|
|
331
|
+
5. In watch mode: polls every N seconds until terminal state or timeout
|
|
332
|
+
|
|
333
|
+
#### Use cases:
|
|
334
|
+
- **CI/CD integration**: Wait for builds before proceeding with deployment
|
|
335
|
+
- **Automation**: Trigger actions based on build results
|
|
336
|
+
- **Scripting**: Check build status in shell scripts
|
|
337
|
+
- **Monitoring**: Poll build status for long-running builds with watch mode
|
|
338
|
+
|
|
252
339
|
### Diff
|
|
253
340
|
```bash
|
|
254
341
|
# Full diff
|
package/package.json
CHANGED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Effect, Schema } from 'effect'
|
|
2
|
+
import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
3
|
+
import type { MessageInfo } from '@/schemas/gerrit'
|
|
4
|
+
import { getChangeIdFromHead, GitError, NoChangeIdError } from '@/utils/git-commit'
|
|
5
|
+
|
|
6
|
+
// Export types for external use
|
|
7
|
+
export type BuildState = 'pending' | 'running' | 'success' | 'failure' | 'not_found'
|
|
8
|
+
|
|
9
|
+
// Watch options (matches gh run watch pattern)
|
|
10
|
+
export type WatchOptions = {
|
|
11
|
+
readonly watch: boolean
|
|
12
|
+
readonly interval: number // seconds
|
|
13
|
+
readonly timeout: number // seconds
|
|
14
|
+
readonly exitStatus: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Timeout error for watch mode
|
|
18
|
+
export class TimeoutError extends Error {
|
|
19
|
+
readonly _tag = 'TimeoutError'
|
|
20
|
+
constructor(message: string) {
|
|
21
|
+
super(message)
|
|
22
|
+
this.name = 'TimeoutError'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Effect Schema for BuildStatus (follows project patterns)
|
|
27
|
+
export const BuildStatus: Schema.Schema<{
|
|
28
|
+
readonly state: 'pending' | 'running' | 'success' | 'failure' | 'not_found'
|
|
29
|
+
}> = Schema.Struct({
|
|
30
|
+
state: Schema.Literal('pending', 'running', 'success', 'failure', 'not_found'),
|
|
31
|
+
})
|
|
32
|
+
export type BuildStatus = Schema.Schema.Type<typeof BuildStatus>
|
|
33
|
+
|
|
34
|
+
// Message patterns for precise matching
|
|
35
|
+
const BUILD_STARTED_PATTERN = /Build\s+Started/i
|
|
36
|
+
const VERIFIED_PLUS_PATTERN = /Verified\s*[+]\s*1/
|
|
37
|
+
const VERIFIED_MINUS_PATTERN = /Verified\s*[-]\s*1/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse messages to determine build status based on "Build Started" and verification messages
|
|
41
|
+
*/
|
|
42
|
+
const parseBuildStatus = (messages: readonly MessageInfo[]): BuildStatus => {
|
|
43
|
+
// Empty messages means change exists but has no activity yet - return pending
|
|
44
|
+
if (messages.length === 0) {
|
|
45
|
+
return { state: 'pending' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find the most recent "Build Started" message
|
|
49
|
+
let lastBuildDate: string | null = null
|
|
50
|
+
for (const msg of messages) {
|
|
51
|
+
if (BUILD_STARTED_PATTERN.test(msg.message)) {
|
|
52
|
+
lastBuildDate = msg.date
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// If no build has started, state is "pending"
|
|
57
|
+
if (!lastBuildDate) {
|
|
58
|
+
return { state: 'pending' }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for verification messages after the build started
|
|
62
|
+
for (const msg of messages) {
|
|
63
|
+
const date = msg.date
|
|
64
|
+
// Gerrit timestamps are ISO 8601 strings (lexicographically sortable)
|
|
65
|
+
if (date <= lastBuildDate) continue
|
|
66
|
+
|
|
67
|
+
if (VERIFIED_PLUS_PATTERN.test(msg.message)) {
|
|
68
|
+
return { state: 'success' }
|
|
69
|
+
} else if (VERIFIED_MINUS_PATTERN.test(msg.message)) {
|
|
70
|
+
return { state: 'failure' }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Build started but no verification yet, state is "running"
|
|
75
|
+
return { state: 'running' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get messages for a change
|
|
80
|
+
*/
|
|
81
|
+
const getMessagesForChange = (
|
|
82
|
+
changeId: string,
|
|
83
|
+
): Effect.Effect<readonly MessageInfo[], ApiError, GerritApiService> =>
|
|
84
|
+
Effect.gen(function* () {
|
|
85
|
+
const gerritApi = yield* GerritApiService
|
|
86
|
+
const messages = yield* gerritApi.getMessages(changeId)
|
|
87
|
+
return messages
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Poll build status until terminal state or timeout
|
|
92
|
+
* Outputs JSON status on each iteration (mimics gh run watch)
|
|
93
|
+
*/
|
|
94
|
+
const pollBuildStatus = (
|
|
95
|
+
changeId: string,
|
|
96
|
+
options: WatchOptions,
|
|
97
|
+
): Effect.Effect<BuildStatus, ApiError | TimeoutError, GerritApiService> =>
|
|
98
|
+
Effect.gen(function* () {
|
|
99
|
+
const startTime = Date.now()
|
|
100
|
+
const timeoutMs = options.timeout * 1000
|
|
101
|
+
|
|
102
|
+
// Initial message to stderr
|
|
103
|
+
yield* Effect.sync(() => {
|
|
104
|
+
console.error(
|
|
105
|
+
`Watching build status (polling every ${options.interval}s, timeout: ${options.timeout}s)...`,
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
while (true) {
|
|
110
|
+
// Check timeout
|
|
111
|
+
const elapsed = Date.now() - startTime
|
|
112
|
+
if (elapsed > timeoutMs) {
|
|
113
|
+
yield* Effect.sync(() => {
|
|
114
|
+
console.error(`Timeout: Build status check exceeded ${options.timeout}s`)
|
|
115
|
+
})
|
|
116
|
+
yield* Effect.fail(
|
|
117
|
+
new TimeoutError(`Build status check timed out after ${options.timeout}s`),
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Fetch and parse status
|
|
122
|
+
const messages = yield* getMessagesForChange(changeId)
|
|
123
|
+
const status = parseBuildStatus(messages)
|
|
124
|
+
|
|
125
|
+
// Check timeout again after API call (in case it took longer than expected)
|
|
126
|
+
const elapsedAfterFetch = Date.now() - startTime
|
|
127
|
+
if (elapsedAfterFetch > timeoutMs) {
|
|
128
|
+
yield* Effect.sync(() => {
|
|
129
|
+
console.error(`Timeout: Build status check exceeded ${options.timeout}s`)
|
|
130
|
+
})
|
|
131
|
+
yield* Effect.fail(
|
|
132
|
+
new TimeoutError(`Build status check timed out after ${options.timeout}s`),
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Output current status to stdout (JSON, like single-check mode)
|
|
137
|
+
yield* Effect.sync(() => {
|
|
138
|
+
process.stdout.write(JSON.stringify(status) + '\n')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Terminal states - return immediately
|
|
142
|
+
if (
|
|
143
|
+
status.state === 'success' ||
|
|
144
|
+
status.state === 'failure' ||
|
|
145
|
+
status.state === 'not_found'
|
|
146
|
+
) {
|
|
147
|
+
yield* Effect.sync(() => {
|
|
148
|
+
console.error(`Build completed with status: ${status.state}`)
|
|
149
|
+
})
|
|
150
|
+
return status
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Non-terminal states - log progress and wait
|
|
154
|
+
const elapsedSeconds = Math.floor(elapsed / 1000)
|
|
155
|
+
yield* Effect.sync(() => {
|
|
156
|
+
console.error(`[${elapsedSeconds}s elapsed] Build status: ${status.state}`)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// Sleep for interval duration
|
|
160
|
+
yield* Effect.sleep(options.interval * 1000)
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build status command with optional watch mode (mimics gh run watch)
|
|
166
|
+
*/
|
|
167
|
+
export const buildStatusCommand = (
|
|
168
|
+
changeId: string | undefined,
|
|
169
|
+
options: Partial<WatchOptions> = {},
|
|
170
|
+
): Effect.Effect<
|
|
171
|
+
void,
|
|
172
|
+
ApiError | Error | GitError | NoChangeIdError | TimeoutError,
|
|
173
|
+
GerritApiService
|
|
174
|
+
> =>
|
|
175
|
+
Effect.gen(function* () {
|
|
176
|
+
// Auto-detect Change-ID from HEAD commit if not provided
|
|
177
|
+
const resolvedChangeId = changeId || (yield* getChangeIdFromHead())
|
|
178
|
+
|
|
179
|
+
// Set defaults (matching gh run watch patterns)
|
|
180
|
+
const watchOpts: WatchOptions = {
|
|
181
|
+
watch: options.watch ?? false,
|
|
182
|
+
interval: Math.max(1, options.interval ?? 10), // Min 1 second
|
|
183
|
+
timeout: Math.max(1, options.timeout ?? 1800), // Min 1 second, default 30 minutes
|
|
184
|
+
exitStatus: options.exitStatus ?? false,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let status: BuildStatus
|
|
188
|
+
|
|
189
|
+
if (watchOpts.watch) {
|
|
190
|
+
// Polling mode - outputs JSON on each iteration
|
|
191
|
+
status = yield* pollBuildStatus(resolvedChangeId, watchOpts)
|
|
192
|
+
} else {
|
|
193
|
+
// Single check mode (existing behavior)
|
|
194
|
+
const messages = yield* getMessagesForChange(resolvedChangeId)
|
|
195
|
+
status = parseBuildStatus(messages)
|
|
196
|
+
|
|
197
|
+
// Output JSON to stdout
|
|
198
|
+
yield* Effect.sync(() => {
|
|
199
|
+
process.stdout.write(JSON.stringify(status) + '\n')
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle exit codes (only non-zero when explicitly requested)
|
|
204
|
+
if (watchOpts.exitStatus && status.state === 'failure') {
|
|
205
|
+
yield* Effect.sync(() => process.exit(1))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Default: exit 0 for all states (success, failure, pending, etc.)
|
|
209
|
+
}).pipe(
|
|
210
|
+
Effect.catchAll((error) => {
|
|
211
|
+
// Timeout error
|
|
212
|
+
if (error instanceof TimeoutError) {
|
|
213
|
+
return Effect.sync(() => {
|
|
214
|
+
console.error(`Error: ${error.message}`)
|
|
215
|
+
process.exit(2)
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 404 - change not found
|
|
220
|
+
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
|
|
221
|
+
const status: BuildStatus = { state: 'not_found' }
|
|
222
|
+
return Effect.sync(() => {
|
|
223
|
+
process.stdout.write(JSON.stringify(status) + '\n')
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Other errors - exit 3
|
|
228
|
+
const errorMessage =
|
|
229
|
+
error instanceof GitError || error instanceof NoChangeIdError || error instanceof Error
|
|
230
|
+
? error.message
|
|
231
|
+
: String(error)
|
|
232
|
+
|
|
233
|
+
return Effect.sync(() => {
|
|
234
|
+
console.error(`Error: ${errorMessage}`)
|
|
235
|
+
process.exit(3)
|
|
236
|
+
})
|
|
237
|
+
}),
|
|
238
|
+
)
|
package/src/cli/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { ConfigServiceLive } from '@/services/config'
|
|
|
31
31
|
import { ReviewStrategyServiceLive } from '@/services/review-strategy'
|
|
32
32
|
import { GitWorktreeServiceLive } from '@/services/git-worktree'
|
|
33
33
|
import { abandonCommand } from './commands/abandon'
|
|
34
|
+
import { buildStatusCommand } from './commands/build-status'
|
|
34
35
|
import { commentCommand } from './commands/comment'
|
|
35
36
|
import { commentsCommand } from './commands/comments'
|
|
36
37
|
import { diffCommand } from './commands/diff'
|
|
@@ -419,6 +420,85 @@ Note: When no change-id is provided, it will be automatically extracted from the
|
|
|
419
420
|
}
|
|
420
421
|
})
|
|
421
422
|
|
|
423
|
+
// build-status command
|
|
424
|
+
program
|
|
425
|
+
.command('build-status [change-id]')
|
|
426
|
+
.description(
|
|
427
|
+
'Check build status from Gerrit messages (auto-detects from HEAD commit if not specified)',
|
|
428
|
+
)
|
|
429
|
+
.option('--watch', 'Watch build status until completion (mimics gh run watch)')
|
|
430
|
+
.option('-i, --interval <seconds>', 'Refresh interval in seconds (default: 10)', '10')
|
|
431
|
+
.option('--timeout <seconds>', 'Maximum wait time in seconds (default: 1800 / 30min)', '1800')
|
|
432
|
+
.option('--exit-status', 'Exit with non-zero status if build fails')
|
|
433
|
+
.addHelpText(
|
|
434
|
+
'after',
|
|
435
|
+
`
|
|
436
|
+
This command parses Gerrit change messages to determine build status.
|
|
437
|
+
It looks for "Build Started" messages and subsequent verification labels.
|
|
438
|
+
|
|
439
|
+
Output is JSON with a "state" field that can be:
|
|
440
|
+
- pending: No build has started yet
|
|
441
|
+
- running: Build started but no verification yet
|
|
442
|
+
- success: Build completed with Verified+1
|
|
443
|
+
- failure: Build completed with Verified-1
|
|
444
|
+
- not_found: Change does not exist
|
|
445
|
+
|
|
446
|
+
Exit codes:
|
|
447
|
+
- 0: Default for all states (like gh run watch)
|
|
448
|
+
- 1: Only when --exit-status is used AND build fails
|
|
449
|
+
- 2: Timeout reached in watch mode
|
|
450
|
+
- 3: API/network errors
|
|
451
|
+
|
|
452
|
+
Examples:
|
|
453
|
+
# Single check (current behavior)
|
|
454
|
+
$ ger build-status 392385
|
|
455
|
+
{"state":"success"}
|
|
456
|
+
|
|
457
|
+
# Watch until completion (outputs JSON on each poll)
|
|
458
|
+
$ ger build-status 392385 --watch
|
|
459
|
+
{"state":"pending"}
|
|
460
|
+
{"state":"running"}
|
|
461
|
+
{"state":"running"}
|
|
462
|
+
{"state":"success"}
|
|
463
|
+
|
|
464
|
+
# Watch with custom interval (check every 5 seconds)
|
|
465
|
+
$ ger build-status --watch --interval 5
|
|
466
|
+
|
|
467
|
+
# Watch with custom timeout (60 minutes)
|
|
468
|
+
$ ger build-status --watch --timeout 3600
|
|
469
|
+
|
|
470
|
+
# Exit with code 1 on failure (for CI/CD pipelines)
|
|
471
|
+
$ ger build-status --watch --exit-status && deploy.sh
|
|
472
|
+
|
|
473
|
+
# Trigger notification when done (like gh run watch pattern)
|
|
474
|
+
$ ger build-status --watch && notify-send 'Build is done!'
|
|
475
|
+
|
|
476
|
+
# Parse final state in scripts
|
|
477
|
+
$ ger build-status --watch | tail -1 | jq -r '.state'
|
|
478
|
+
success
|
|
479
|
+
|
|
480
|
+
Note: When no change-id is provided, it will be automatically extracted from the
|
|
481
|
+
Change-ID footer in your HEAD commit.`,
|
|
482
|
+
)
|
|
483
|
+
.action(async (changeId, cmdOptions) => {
|
|
484
|
+
try {
|
|
485
|
+
const effect = buildStatusCommand(changeId, {
|
|
486
|
+
watch: cmdOptions.watch,
|
|
487
|
+
interval: Number.parseInt(cmdOptions.interval, 10),
|
|
488
|
+
timeout: Number.parseInt(cmdOptions.timeout, 10),
|
|
489
|
+
exitStatus: cmdOptions.exitStatus,
|
|
490
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(ConfigServiceLive))
|
|
491
|
+
await Effect.runPromise(effect)
|
|
492
|
+
} catch (error) {
|
|
493
|
+
// Errors are handled within the command itself
|
|
494
|
+
// This catch is just for any unexpected errors
|
|
495
|
+
if (error instanceof Error && error.message !== 'Process exited') {
|
|
496
|
+
console.error('✗ Unexpected error:', error.message)
|
|
497
|
+
process.exit(3)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
})
|
|
501
|
+
|
|
422
502
|
// extract-url command
|
|
423
503
|
program
|
|
424
504
|
.command('extract-url <pattern> [change-id]')
|