@aaronshaf/ger 0.2.1 → 0.2.2
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/EXAMPLES.md +409 -0
- package/index.ts +219 -0
- package/package.json +46 -1
- package/src/services/git-worktree.ts +14 -8
- package/src/utils/index.ts +55 -0
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Programmatic Usage Examples
|
|
2
|
+
|
|
3
|
+
This package can be used both as a CLI tool and as a library. Below are examples of using `@aaronshaf/ger` programmatically with Effect-TS.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @aaronshaf/ger
|
|
9
|
+
# or
|
|
10
|
+
npm install @aaronshaf/ger
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Setup
|
|
14
|
+
|
|
15
|
+
All services in this package are built with Effect-TS, providing type-safe, composable operations.
|
|
16
|
+
|
|
17
|
+
### Import the services
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Effect, pipe } from 'effect'
|
|
21
|
+
import {
|
|
22
|
+
GerritApiService,
|
|
23
|
+
GerritApiServiceLive,
|
|
24
|
+
ConfigServiceLive,
|
|
25
|
+
type ChangeInfo,
|
|
26
|
+
} from '@aaronshaf/ger'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
### Using Environment Variables
|
|
32
|
+
|
|
33
|
+
Set these environment variables before running your program:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export GERRIT_HOST="https://gerrit.example.com"
|
|
37
|
+
export GERRIT_USERNAME="your-username"
|
|
38
|
+
export GERRIT_PASSWORD="your-http-password"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Using File-Based Config
|
|
42
|
+
|
|
43
|
+
Or run the CLI once to set up configuration:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ger setup
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This stores credentials in `~/.ger/config.json`.
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
### 1. Get Change Information
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { Effect, pipe } from 'effect'
|
|
57
|
+
import {
|
|
58
|
+
GerritApiService,
|
|
59
|
+
GerritApiServiceLive,
|
|
60
|
+
ConfigServiceLive,
|
|
61
|
+
} from '@aaronshaf/ger'
|
|
62
|
+
|
|
63
|
+
const getChangeDetails = (changeId: string) =>
|
|
64
|
+
Effect.gen(function* () {
|
|
65
|
+
const api = yield* GerritApiService
|
|
66
|
+
const change = yield* api.getChange(changeId)
|
|
67
|
+
|
|
68
|
+
console.log(`Change: ${change.subject}`)
|
|
69
|
+
console.log(`Status: ${change.status}`)
|
|
70
|
+
console.log(`Owner: ${change.owner?.name || 'Unknown'}`)
|
|
71
|
+
|
|
72
|
+
return change
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Run the program
|
|
76
|
+
const program = pipe(
|
|
77
|
+
getChangeDetails('12345'),
|
|
78
|
+
Effect.provide(GerritApiServiceLive),
|
|
79
|
+
Effect.provide(ConfigServiceLive)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
Effect.runPromise(program)
|
|
83
|
+
.then(() => console.log('Done!'))
|
|
84
|
+
.catch(console.error)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. List Open Changes
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { Effect, pipe } from 'effect'
|
|
91
|
+
import {
|
|
92
|
+
GerritApiService,
|
|
93
|
+
GerritApiServiceLive,
|
|
94
|
+
ConfigServiceLive,
|
|
95
|
+
} from '@aaronshaf/ger'
|
|
96
|
+
|
|
97
|
+
const listMyChanges = Effect.gen(function* () {
|
|
98
|
+
const api = yield* GerritApiService
|
|
99
|
+
|
|
100
|
+
// Query for your open changes
|
|
101
|
+
const changes = yield* api.listChanges('is:open owner:self')
|
|
102
|
+
|
|
103
|
+
console.log(`You have ${changes.length} open changes:`)
|
|
104
|
+
for (const change of changes) {
|
|
105
|
+
console.log(` - #${change._number}: ${change.subject}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return changes
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const program = pipe(
|
|
112
|
+
listMyChanges,
|
|
113
|
+
Effect.provide(GerritApiServiceLive),
|
|
114
|
+
Effect.provide(ConfigServiceLive)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
Effect.runPromise(program).catch(console.error)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 3. Post a Comment
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { Effect, pipe } from 'effect'
|
|
124
|
+
import {
|
|
125
|
+
GerritApiService,
|
|
126
|
+
GerritApiServiceLive,
|
|
127
|
+
ConfigServiceLive,
|
|
128
|
+
type ReviewInput,
|
|
129
|
+
} from '@aaronshaf/ger'
|
|
130
|
+
|
|
131
|
+
const postComment = (changeId: string) =>
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
const api = yield* GerritApiService
|
|
134
|
+
|
|
135
|
+
const review: ReviewInput = {
|
|
136
|
+
message: 'Looks good to me!',
|
|
137
|
+
labels: {
|
|
138
|
+
'Code-Review': 1,
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
yield* api.postReview(changeId, review)
|
|
143
|
+
console.log('Comment posted successfully!')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const program = pipe(
|
|
147
|
+
postComment('12345'),
|
|
148
|
+
Effect.provide(GerritApiServiceLive),
|
|
149
|
+
Effect.provide(ConfigServiceLive)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
Effect.runPromise(program).catch(console.error)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 4. Post Inline Comments
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { Effect, pipe } from 'effect'
|
|
159
|
+
import {
|
|
160
|
+
GerritApiService,
|
|
161
|
+
GerritApiServiceLive,
|
|
162
|
+
ConfigServiceLive,
|
|
163
|
+
type ReviewInput,
|
|
164
|
+
} from '@aaronshaf/ger'
|
|
165
|
+
|
|
166
|
+
const postInlineComments = (changeId: string) =>
|
|
167
|
+
Effect.gen(function* () {
|
|
168
|
+
const api = yield* GerritApiService
|
|
169
|
+
|
|
170
|
+
const review: ReviewInput = {
|
|
171
|
+
message: 'Review complete',
|
|
172
|
+
comments: {
|
|
173
|
+
'src/api.ts': [
|
|
174
|
+
{
|
|
175
|
+
line: 42,
|
|
176
|
+
message: 'Consider using const here for immutability',
|
|
177
|
+
unresolved: false,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
line: 55,
|
|
181
|
+
message: 'This could cause a security issue',
|
|
182
|
+
unresolved: true,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
'src/utils.ts': [
|
|
186
|
+
{
|
|
187
|
+
line: 10,
|
|
188
|
+
message: 'Nice refactor!',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
yield* api.postReview(changeId, review)
|
|
195
|
+
console.log('Inline comments posted!')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const program = pipe(
|
|
199
|
+
postInlineComments('12345'),
|
|
200
|
+
Effect.provide(GerritApiServiceLive),
|
|
201
|
+
Effect.provide(ConfigServiceLive)
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
Effect.runPromise(program).catch(console.error)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 5. Get Diff for a Change
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { Effect, pipe } from 'effect'
|
|
211
|
+
import {
|
|
212
|
+
GerritApiService,
|
|
213
|
+
GerritApiServiceLive,
|
|
214
|
+
ConfigServiceLive,
|
|
215
|
+
type DiffOptions,
|
|
216
|
+
} from '@aaronshaf/ger'
|
|
217
|
+
|
|
218
|
+
const getDiff = (changeId: string) =>
|
|
219
|
+
Effect.gen(function* () {
|
|
220
|
+
const api = yield* GerritApiService
|
|
221
|
+
|
|
222
|
+
// Get unified diff format (default)
|
|
223
|
+
const diff = yield* api.getDiff(changeId, { format: 'unified' })
|
|
224
|
+
console.log('Diff:', diff)
|
|
225
|
+
|
|
226
|
+
// Or get list of changed files
|
|
227
|
+
const files = yield* api.getDiff(changeId, { format: 'files' })
|
|
228
|
+
console.log('Changed files:', files)
|
|
229
|
+
|
|
230
|
+
return diff
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const program = pipe(
|
|
234
|
+
getDiff('12345'),
|
|
235
|
+
Effect.provide(GerritApiServiceLive),
|
|
236
|
+
Effect.provide(ConfigServiceLive)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
Effect.runPromise(program).catch(console.error)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 6. Test Connection
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { Effect, pipe } from 'effect'
|
|
246
|
+
import {
|
|
247
|
+
GerritApiService,
|
|
248
|
+
GerritApiServiceLive,
|
|
249
|
+
ConfigServiceLive,
|
|
250
|
+
} from '@aaronshaf/ger'
|
|
251
|
+
|
|
252
|
+
const testConnection = Effect.gen(function* () {
|
|
253
|
+
const api = yield* GerritApiService
|
|
254
|
+
const isConnected = yield* api.testConnection
|
|
255
|
+
|
|
256
|
+
if (isConnected) {
|
|
257
|
+
console.log('✓ Connected to Gerrit!')
|
|
258
|
+
} else {
|
|
259
|
+
console.log('✗ Connection failed')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return isConnected
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
const program = pipe(
|
|
266
|
+
testConnection,
|
|
267
|
+
Effect.provide(GerritApiServiceLive),
|
|
268
|
+
Effect.provide(ConfigServiceLive)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
Effect.runPromise(program).catch(console.error)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 7. Error Handling with Effect
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { Effect, pipe, Console } from 'effect'
|
|
278
|
+
import {
|
|
279
|
+
GerritApiService,
|
|
280
|
+
GerritApiServiceLive,
|
|
281
|
+
ConfigServiceLive,
|
|
282
|
+
ApiError,
|
|
283
|
+
ConfigError,
|
|
284
|
+
} from '@aaronshaf/ger'
|
|
285
|
+
|
|
286
|
+
const safeGetChange = (changeId: string) =>
|
|
287
|
+
Effect.gen(function* () {
|
|
288
|
+
const api = yield* GerritApiService
|
|
289
|
+
const change = yield* api.getChange(changeId)
|
|
290
|
+
return change
|
|
291
|
+
}).pipe(
|
|
292
|
+
Effect.catchTag('ApiError', (error) =>
|
|
293
|
+
Console.error(`API Error: ${error.message}`).pipe(
|
|
294
|
+
Effect.map(() => null)
|
|
295
|
+
)
|
|
296
|
+
),
|
|
297
|
+
Effect.catchTag('ConfigError', (error) =>
|
|
298
|
+
Console.error(`Config Error: ${error.message}`).pipe(
|
|
299
|
+
Effect.map(() => null)
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const program = pipe(
|
|
305
|
+
safeGetChange('invalid-change'),
|
|
306
|
+
Effect.provide(GerritApiServiceLive),
|
|
307
|
+
Effect.provide(ConfigServiceLive)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
Effect.runPromise(program)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### 8. Using Utilities
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import {
|
|
317
|
+
normalizeChangeIdentifier,
|
|
318
|
+
extractChangeIdFromCommitMessage,
|
|
319
|
+
extractChangeNumber,
|
|
320
|
+
normalizeGerritHost,
|
|
321
|
+
} from '@aaronshaf/ger'
|
|
322
|
+
|
|
323
|
+
// Normalize change identifiers
|
|
324
|
+
const normalized = normalizeChangeIdentifier('12345')
|
|
325
|
+
// or
|
|
326
|
+
const normalizedId = normalizeChangeIdentifier('If5a3ae8cb5a107e187447802358417f311d0c4b1')
|
|
327
|
+
|
|
328
|
+
// Extract change ID from commit message
|
|
329
|
+
const commitMsg = `feat: add feature
|
|
330
|
+
|
|
331
|
+
Change-Id: If5a3ae8cb5a107e187447802358417f311d0c4b1`
|
|
332
|
+
|
|
333
|
+
const changeId = extractChangeIdFromCommitMessage(commitMsg)
|
|
334
|
+
console.log(changeId) // "If5a3ae8cb5a107e187447802358417f311d0c4b1"
|
|
335
|
+
|
|
336
|
+
// Extract change number from Gerrit URL
|
|
337
|
+
const url = 'https://gerrit.example.com/c/project/+/12345'
|
|
338
|
+
const changeNumber = extractChangeNumber(url)
|
|
339
|
+
console.log(changeNumber) // "12345"
|
|
340
|
+
|
|
341
|
+
// Normalize Gerrit host
|
|
342
|
+
const host = normalizeGerritHost('gerrit.example.com')
|
|
343
|
+
console.log(host) // "https://gerrit.example.com"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 9. Working with Schemas
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { Schema } from '@effect/schema'
|
|
350
|
+
import { Effect } from 'effect'
|
|
351
|
+
import { ChangeInfo, ReviewInput } from '@aaronshaf/ger'
|
|
352
|
+
|
|
353
|
+
// Validate and decode API responses
|
|
354
|
+
const validateChange = (data: unknown) =>
|
|
355
|
+
Schema.decodeUnknown(ChangeInfo)(data)
|
|
356
|
+
|
|
357
|
+
// Validate review input before sending
|
|
358
|
+
const validateReview = (review: unknown) =>
|
|
359
|
+
Schema.decodeUnknown(ReviewInput)(review)
|
|
360
|
+
|
|
361
|
+
// Use in an Effect program
|
|
362
|
+
const safeReview = Effect.gen(function* () {
|
|
363
|
+
const review = {
|
|
364
|
+
message: 'LGTM',
|
|
365
|
+
labels: { 'Code-Review': 2 },
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const validated = yield* validateReview(review)
|
|
369
|
+
console.log('Review is valid:', validated)
|
|
370
|
+
|
|
371
|
+
return validated
|
|
372
|
+
})
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Direct Module Access
|
|
376
|
+
|
|
377
|
+
You can also import directly from specific modules:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Import from specific services
|
|
381
|
+
import { GerritApiService, GerritApiServiceLive } from '@aaronshaf/ger/api'
|
|
382
|
+
import { ConfigService, ConfigServiceLive } from '@aaronshaf/ger/services/config'
|
|
383
|
+
|
|
384
|
+
// Import from specific schemas
|
|
385
|
+
import { ChangeInfo, ReviewInput } from '@aaronshaf/ger/schemas/gerrit'
|
|
386
|
+
|
|
387
|
+
// Import utilities
|
|
388
|
+
import { normalizeChangeIdentifier, extractChangeNumber } from '@aaronshaf/ger/utils'
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## TypeScript Configuration
|
|
392
|
+
|
|
393
|
+
Make sure your `tsconfig.json` includes:
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"compilerOptions": {
|
|
398
|
+
"moduleResolution": "bundler",
|
|
399
|
+
"allowImportingTsExtensions": true,
|
|
400
|
+
"strict": true
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## More Information
|
|
406
|
+
|
|
407
|
+
- See the [main README](./README.md) for CLI usage
|
|
408
|
+
- Check out the [Effect documentation](https://effect.website/) to learn more about Effect-TS
|
|
409
|
+
- View the type definitions in your IDE for detailed API documentation
|
package/index.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @aaronshaf/ger - Gerrit CLI and SDK
|
|
3
|
+
*
|
|
4
|
+
* This package provides both a CLI tool and a programmatic API for interacting with Gerrit Code Review.
|
|
5
|
+
* Built with Effect-TS for type-safe, composable operations.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*
|
|
9
|
+
* @example Basic usage with Effect
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { Effect, pipe } from 'effect'
|
|
12
|
+
* import {
|
|
13
|
+
* GerritApiService,
|
|
14
|
+
* GerritApiServiceLive,
|
|
15
|
+
* ConfigServiceLive,
|
|
16
|
+
* } from '@aaronshaf/ger'
|
|
17
|
+
*
|
|
18
|
+
* const program = Effect.gen(function* () {
|
|
19
|
+
* const api = yield* GerritApiService
|
|
20
|
+
* const change = yield* api.getChange('12345')
|
|
21
|
+
* console.log(change.subject)
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* const runnable = pipe(
|
|
25
|
+
* program,
|
|
26
|
+
* Effect.provide(GerritApiServiceLive),
|
|
27
|
+
* Effect.provide(ConfigServiceLive)
|
|
28
|
+
* )
|
|
29
|
+
*
|
|
30
|
+
* Effect.runPromise(runnable)
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Core API Service
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
// Service tag and implementation
|
|
40
|
+
GerritApiService,
|
|
41
|
+
GerritApiServiceLive,
|
|
42
|
+
// Types
|
|
43
|
+
type GerritApiServiceImpl,
|
|
44
|
+
// Errors
|
|
45
|
+
ApiError,
|
|
46
|
+
type ApiErrorFields,
|
|
47
|
+
} from './src/api/gerrit'
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Configuration Service
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
// Service tag and implementation
|
|
55
|
+
ConfigService,
|
|
56
|
+
ConfigServiceLive,
|
|
57
|
+
// Types
|
|
58
|
+
type ConfigServiceImpl,
|
|
59
|
+
// Errors
|
|
60
|
+
ConfigError,
|
|
61
|
+
type ConfigErrorFields,
|
|
62
|
+
} from './src/services/config'
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Review Strategy Service
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
// Strategy types
|
|
70
|
+
type ReviewStrategy,
|
|
71
|
+
// Built-in strategies
|
|
72
|
+
claudeCliStrategy,
|
|
73
|
+
geminiCliStrategy,
|
|
74
|
+
openCodeCliStrategy,
|
|
75
|
+
// Service
|
|
76
|
+
ReviewStrategyService,
|
|
77
|
+
ReviewStrategyServiceLive,
|
|
78
|
+
type ReviewStrategyServiceImpl,
|
|
79
|
+
// Errors
|
|
80
|
+
ReviewStrategyError,
|
|
81
|
+
type ReviewStrategyErrorFields,
|
|
82
|
+
} from './src/services/review-strategy'
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Git Worktree Service
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
// Service tag and implementation
|
|
90
|
+
GitWorktreeService,
|
|
91
|
+
GitWorktreeServiceLive,
|
|
92
|
+
type GitWorktreeServiceImpl,
|
|
93
|
+
// Types
|
|
94
|
+
type WorktreeInfo,
|
|
95
|
+
// Errors
|
|
96
|
+
WorktreeCreationError,
|
|
97
|
+
type WorktreeCreationErrorFields,
|
|
98
|
+
PatchsetFetchError,
|
|
99
|
+
type PatchsetFetchErrorFields,
|
|
100
|
+
DirtyRepoError,
|
|
101
|
+
type DirtyRepoErrorFields,
|
|
102
|
+
NotGitRepoError,
|
|
103
|
+
type NotGitRepoErrorFields,
|
|
104
|
+
type GitWorktreeError,
|
|
105
|
+
} from './src/services/git-worktree'
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Schemas and Types
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
// Authentication
|
|
113
|
+
GerritCredentials,
|
|
114
|
+
type GerritCredentials as GerritCredentialsType,
|
|
115
|
+
// Changes
|
|
116
|
+
ChangeInfo,
|
|
117
|
+
type ChangeInfo as ChangeInfoType,
|
|
118
|
+
// Comments
|
|
119
|
+
CommentInput,
|
|
120
|
+
type CommentInput as CommentInputType,
|
|
121
|
+
CommentInfo,
|
|
122
|
+
type CommentInfo as CommentInfoType,
|
|
123
|
+
// Messages
|
|
124
|
+
MessageInfo,
|
|
125
|
+
type MessageInfo as MessageInfoType,
|
|
126
|
+
// Reviews
|
|
127
|
+
ReviewInput,
|
|
128
|
+
type ReviewInput as ReviewInputType,
|
|
129
|
+
// Files and Diffs
|
|
130
|
+
FileInfo,
|
|
131
|
+
type FileInfo as FileInfoType,
|
|
132
|
+
FileDiffContent,
|
|
133
|
+
type FileDiffContent as FileDiffContentType,
|
|
134
|
+
RevisionInfo,
|
|
135
|
+
type RevisionInfo as RevisionInfoType,
|
|
136
|
+
// Diff Options
|
|
137
|
+
DiffFormat,
|
|
138
|
+
type DiffFormat as DiffFormatType,
|
|
139
|
+
DiffOptions,
|
|
140
|
+
type DiffOptions as DiffOptionsType,
|
|
141
|
+
DiffCommandOptions,
|
|
142
|
+
type DiffCommandOptions as DiffCommandOptionsType,
|
|
143
|
+
// Errors
|
|
144
|
+
GerritError,
|
|
145
|
+
type GerritError as GerritErrorType,
|
|
146
|
+
} from './src/schemas/gerrit'
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
// Config schemas
|
|
150
|
+
AppConfig,
|
|
151
|
+
type AppConfig as AppConfigType,
|
|
152
|
+
AiConfig,
|
|
153
|
+
type AiConfig as AiConfigType,
|
|
154
|
+
// Utilities
|
|
155
|
+
aiConfigFromFlat,
|
|
156
|
+
migrateFromNestedConfig,
|
|
157
|
+
} from './src/schemas/config'
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Utilities
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
// Change ID handling
|
|
165
|
+
normalizeChangeIdentifier,
|
|
166
|
+
isChangeId,
|
|
167
|
+
isChangeNumber,
|
|
168
|
+
isValidChangeIdentifier,
|
|
169
|
+
getIdentifierType,
|
|
170
|
+
} from './src/utils/change-id'
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
// Git commit utilities
|
|
174
|
+
extractChangeIdFromCommitMessage,
|
|
175
|
+
getLastCommitMessage,
|
|
176
|
+
getChangeIdFromHead,
|
|
177
|
+
GitError,
|
|
178
|
+
NoChangeIdError,
|
|
179
|
+
} from './src/utils/git-commit'
|
|
180
|
+
|
|
181
|
+
export {
|
|
182
|
+
// URL parsing
|
|
183
|
+
extractChangeNumber,
|
|
184
|
+
normalizeGerritHost,
|
|
185
|
+
isValidChangeId,
|
|
186
|
+
} from './src/utils/url-parser'
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
// Message filtering
|
|
190
|
+
filterMeaningfulMessages,
|
|
191
|
+
sortMessagesByDate,
|
|
192
|
+
} from './src/utils/message-filters'
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
// Shell safety
|
|
196
|
+
sanitizeCDATA,
|
|
197
|
+
} from './src/utils/shell-safety'
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
// Formatters
|
|
201
|
+
formatDate,
|
|
202
|
+
getStatusIndicator,
|
|
203
|
+
colors,
|
|
204
|
+
} from './src/utils/formatters'
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
// Comment formatters
|
|
208
|
+
formatCommentsPretty,
|
|
209
|
+
formatCommentsXml,
|
|
210
|
+
type CommentWithContext,
|
|
211
|
+
} from './src/utils/comment-formatters'
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
// Diff formatters
|
|
215
|
+
formatDiffPretty,
|
|
216
|
+
formatDiffSummary,
|
|
217
|
+
formatFilesList,
|
|
218
|
+
extractDiffStats,
|
|
219
|
+
} from './src/utils/diff-formatters'
|
package/package.json
CHANGED
|
@@ -1,11 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaronshaf/ger",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"gerrit",
|
|
7
|
+
"code-review",
|
|
8
|
+
"cli",
|
|
9
|
+
"sdk",
|
|
10
|
+
"effect",
|
|
11
|
+
"effect-ts",
|
|
12
|
+
"typescript",
|
|
13
|
+
"api-client"
|
|
14
|
+
],
|
|
4
15
|
"module": "index.ts",
|
|
5
16
|
"type": "module",
|
|
6
17
|
"bin": {
|
|
7
18
|
"ger": "./bin/ger"
|
|
8
19
|
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": "./index.ts",
|
|
23
|
+
"types": "./index.ts"
|
|
24
|
+
},
|
|
25
|
+
"./api": {
|
|
26
|
+
"import": "./src/api/gerrit.ts",
|
|
27
|
+
"types": "./src/api/gerrit.ts"
|
|
28
|
+
},
|
|
29
|
+
"./services/config": {
|
|
30
|
+
"import": "./src/services/config.ts",
|
|
31
|
+
"types": "./src/services/config.ts"
|
|
32
|
+
},
|
|
33
|
+
"./services/review-strategy": {
|
|
34
|
+
"import": "./src/services/review-strategy.ts",
|
|
35
|
+
"types": "./src/services/review-strategy.ts"
|
|
36
|
+
},
|
|
37
|
+
"./services/git-worktree": {
|
|
38
|
+
"import": "./src/services/git-worktree.ts",
|
|
39
|
+
"types": "./src/services/git-worktree.ts"
|
|
40
|
+
},
|
|
41
|
+
"./schemas/gerrit": {
|
|
42
|
+
"import": "./src/schemas/gerrit.ts",
|
|
43
|
+
"types": "./src/schemas/gerrit.ts"
|
|
44
|
+
},
|
|
45
|
+
"./schemas/config": {
|
|
46
|
+
"import": "./src/schemas/config.ts",
|
|
47
|
+
"types": "./src/schemas/config.ts"
|
|
48
|
+
},
|
|
49
|
+
"./utils": {
|
|
50
|
+
"import": "./src/utils/index.ts",
|
|
51
|
+
"types": "./src/utils/index.ts"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
9
54
|
"repository": {
|
|
10
55
|
"type": "git",
|
|
11
56
|
"url": "git+https://github.com/aaronshaf/ger.git"
|
|
@@ -84,7 +84,11 @@ export class NotGitRepoError
|
|
|
84
84
|
readonly name = 'NotGitRepoError'
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export type
|
|
87
|
+
export type GitWorktreeError =
|
|
88
|
+
| WorktreeCreationError
|
|
89
|
+
| PatchsetFetchError
|
|
90
|
+
| DirtyRepoError
|
|
91
|
+
| NotGitRepoError
|
|
88
92
|
|
|
89
93
|
// Worktree info
|
|
90
94
|
export interface WorktreeInfo {
|
|
@@ -99,8 +103,8 @@ export interface WorktreeInfo {
|
|
|
99
103
|
const runGitCommand = (
|
|
100
104
|
args: string[],
|
|
101
105
|
options: { cwd?: string } = {},
|
|
102
|
-
): Effect.Effect<string,
|
|
103
|
-
Effect.async<string,
|
|
106
|
+
): Effect.Effect<string, GitWorktreeError, never> =>
|
|
107
|
+
Effect.async<string, GitWorktreeError, never>((resume) => {
|
|
104
108
|
const child = spawn('git', args, {
|
|
105
109
|
cwd: options.cwd || process.cwd(),
|
|
106
110
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -182,7 +186,7 @@ const buildRefspec = (changeNumber: string, patchsetNumber: number = 1): string
|
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
// Get the current HEAD commit hash to avoid branch conflicts
|
|
185
|
-
const getCurrentCommit = (): Effect.Effect<string,
|
|
189
|
+
const getCurrentCommit = (): Effect.Effect<string, GitWorktreeError, never> =>
|
|
186
190
|
pipe(
|
|
187
191
|
runGitCommand(['rev-parse', 'HEAD']),
|
|
188
192
|
Effect.map((output) => output.trim()),
|
|
@@ -226,11 +230,13 @@ const getLatestPatchsetNumber = (
|
|
|
226
230
|
|
|
227
231
|
// GitWorktreeService implementation
|
|
228
232
|
export interface GitWorktreeServiceImpl {
|
|
229
|
-
validatePreconditions: () => Effect.Effect<void,
|
|
230
|
-
createWorktree: (changeId: string) => Effect.Effect<WorktreeInfo,
|
|
231
|
-
fetchAndCheckoutPatchset: (
|
|
233
|
+
validatePreconditions: () => Effect.Effect<void, GitWorktreeError, never>
|
|
234
|
+
createWorktree: (changeId: string) => Effect.Effect<WorktreeInfo, GitWorktreeError, never>
|
|
235
|
+
fetchAndCheckoutPatchset: (
|
|
236
|
+
worktreeInfo: WorktreeInfo,
|
|
237
|
+
) => Effect.Effect<void, GitWorktreeError, never>
|
|
232
238
|
cleanup: (worktreeInfo: WorktreeInfo) => Effect.Effect<void, never, never>
|
|
233
|
-
getChangedFiles: () => Effect.Effect<string[],
|
|
239
|
+
getChangedFiles: () => Effect.Effect<string[], GitWorktreeError, never>
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
const GitWorktreeServiceImplLive: GitWorktreeServiceImpl = {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for working with Gerrit
|
|
3
|
+
* @module utils
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Change ID utilities
|
|
7
|
+
export {
|
|
8
|
+
normalizeChangeIdentifier,
|
|
9
|
+
isChangeId,
|
|
10
|
+
isChangeNumber,
|
|
11
|
+
isValidChangeIdentifier,
|
|
12
|
+
getIdentifierType,
|
|
13
|
+
} from './change-id'
|
|
14
|
+
|
|
15
|
+
// Git commit utilities
|
|
16
|
+
export {
|
|
17
|
+
extractChangeIdFromCommitMessage,
|
|
18
|
+
getLastCommitMessage,
|
|
19
|
+
getChangeIdFromHead,
|
|
20
|
+
GitError,
|
|
21
|
+
NoChangeIdError,
|
|
22
|
+
} from './git-commit'
|
|
23
|
+
|
|
24
|
+
// URL parsing
|
|
25
|
+
export {
|
|
26
|
+
extractChangeNumber,
|
|
27
|
+
normalizeGerritHost,
|
|
28
|
+
isValidChangeId,
|
|
29
|
+
} from './url-parser'
|
|
30
|
+
|
|
31
|
+
// Message filtering
|
|
32
|
+
export { filterMeaningfulMessages, sortMessagesByDate } from './message-filters'
|
|
33
|
+
|
|
34
|
+
// Shell safety
|
|
35
|
+
export { sanitizeCDATA } from './shell-safety'
|
|
36
|
+
|
|
37
|
+
// Formatters
|
|
38
|
+
export {
|
|
39
|
+
formatDate,
|
|
40
|
+
getStatusIndicator,
|
|
41
|
+
colors,
|
|
42
|
+
} from './formatters'
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
formatCommentsPretty,
|
|
46
|
+
formatCommentsXml,
|
|
47
|
+
type CommentWithContext,
|
|
48
|
+
} from './comment-formatters'
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
formatDiffPretty,
|
|
52
|
+
formatDiffSummary,
|
|
53
|
+
formatFilesList,
|
|
54
|
+
extractDiffStats,
|
|
55
|
+
} from './diff-formatters'
|