@aaronshaf/ger
Version:
Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS
458 lines (358 loc) • 10.6 kB
Markdown
# Programmatic Usage Examples
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.
## Installation
```bash
bun add @aaronshaf/ger
# or
npm install @aaronshaf/ger
```
## Basic Setup
All services in this package are built with Effect-TS, providing type-safe, composable operations.
### Import the services
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
type ChangeInfo,
} from '@aaronshaf/ger'
```
## Configuration
### Using Environment Variables
Set these environment variables before running your program:
```bash
export GERRIT_HOST="https://gerrit.example.com"
export GERRIT_USERNAME="your-username"
export GERRIT_PASSWORD="your-http-password"
```
### Using File-Based Config
Or run the CLI once to set up configuration:
```bash
ger setup
```
This stores credentials in `~/.ger/config.json`.
## Examples
### 1. Get Change Information
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
} from '@aaronshaf/ger'
const getChangeDetails = (changeId: string) =>
Effect.gen(function* () {
const api = yield* GerritApiService
const change = yield* api.getChange(changeId)
console.log(`Change: ${change.subject}`)
console.log(`Status: ${change.status}`)
console.log(`Owner: ${change.owner?.name || 'Unknown'}`)
return change
})
// Run the program
const program = pipe(
getChangeDetails('12345'),
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program)
.then(() => console.log('Done!'))
.catch(console.error)
```
### 2. List Open Changes
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
} from '@aaronshaf/ger'
const listMyChanges = Effect.gen(function* () {
const api = yield* GerritApiService
// Query for your open changes
const changes = yield* api.listChanges('is:open owner:self')
console.log(`You have ${changes.length} open changes:`)
for (const change of changes) {
console.log(` - #${change._number}: ${change.subject}`)
}
return changes
})
const program = pipe(
listMyChanges,
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program).catch(console.error)
```
### 2b. Search Changes with Query Syntax
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
} from '@aaronshaf/ger'
const searchChanges = (query: string, limit = 25) =>
Effect.gen(function* () {
const api = yield* GerritApiService
// Use Gerrit query syntax to search changes
const fullQuery = query.includes('limit:') ? query : `${query} limit:${limit}`
const changes = yield* api.listChanges(fullQuery)
// Group by project for organized output
const byProject = new Map<string, typeof changes>()
for (const change of changes) {
const existing = byProject.get(change.project) ?? []
existing.push(change)
byProject.set(change.project, existing)
}
console.log(`Found ${changes.length} changes:`)
for (const [project, projectChanges] of byProject) {
console.log(`\n${project}:`)
for (const change of projectChanges) {
console.log(` #${change._number} - ${change.subject} (${change.status})`)
}
}
return changes
})
// Example queries
const program = pipe(
// Search for merged changes in the last week
searchChanges('status:merged age:7d', 10),
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program).catch(console.error)
```
### 3. Post a Comment
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
type ReviewInput,
} from '@aaronshaf/ger'
const postComment = (changeId: string) =>
Effect.gen(function* () {
const api = yield* GerritApiService
const review: ReviewInput = {
message: 'Looks good to me!',
labels: {
'Code-Review': 1,
},
}
yield* api.postReview(changeId, review)
console.log('Comment posted successfully!')
})
const program = pipe(
postComment('12345'),
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program).catch(console.error)
```
### 4. Post Inline Comments
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
type ReviewInput,
} from '@aaronshaf/ger'
const postInlineComments = (changeId: string) =>
Effect.gen(function* () {
const api = yield* GerritApiService
const review: ReviewInput = {
message: 'Review complete',
comments: {
'src/api.ts': [
{
line: 42,
message: 'Consider using const here for immutability',
unresolved: false,
},
{
line: 55,
message: 'This could cause a security issue',
unresolved: true,
},
],
'src/utils.ts': [
{
line: 10,
message: 'Nice refactor!',
},
],
},
}
yield* api.postReview(changeId, review)
console.log('Inline comments posted!')
})
const program = pipe(
postInlineComments('12345'),
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program).catch(console.error)
```
### 5. Get Diff for a Change
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
type DiffOptions,
} from '@aaronshaf/ger'
const getDiff = (changeId: string) =>
Effect.gen(function* () {
const api = yield* GerritApiService
// Get unified diff format (default)
const diff = yield* api.getDiff(changeId, { format: 'unified' })
console.log('Diff:', diff)
// Or get list of changed files
const files = yield* api.getDiff(changeId, { format: 'files' })
console.log('Changed files:', files)
return diff
})
const program = pipe(
getDiff('12345'),
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program).catch(console.error)
```
### 6. Test Connection
```typescript
import { Effect, pipe } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
} from '@aaronshaf/ger'
const testConnection = Effect.gen(function* () {
const api = yield* GerritApiService
const isConnected = yield* api.testConnection
if (isConnected) {
console.log('✓ Connected to Gerrit!')
} else {
console.log('✗ Connection failed')
}
return isConnected
})
const program = pipe(
testConnection,
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program).catch(console.error)
```
### 7. Error Handling with Effect
```typescript
import { Effect, pipe, Console } from 'effect'
import {
GerritApiService,
GerritApiServiceLive,
ConfigServiceLive,
ApiError,
ConfigError,
} from '@aaronshaf/ger'
const safeGetChange = (changeId: string) =>
Effect.gen(function* () {
const api = yield* GerritApiService
const change = yield* api.getChange(changeId)
return change
}).pipe(
Effect.catchTag('ApiError', (error) =>
Console.error(`API Error: ${error.message}`).pipe(
Effect.map(() => null)
)
),
Effect.catchTag('ConfigError', (error) =>
Console.error(`Config Error: ${error.message}`).pipe(
Effect.map(() => null)
)
)
)
const program = pipe(
safeGetChange('invalid-change'),
Effect.provide(GerritApiServiceLive),
Effect.provide(ConfigServiceLive)
)
Effect.runPromise(program)
```
### 8. Using Utilities
```typescript
import {
normalizeChangeIdentifier,
extractChangeIdFromCommitMessage,
extractChangeNumber,
normalizeGerritHost,
} from '@aaronshaf/ger'
// Normalize change identifiers
const normalized = normalizeChangeIdentifier('12345')
// or
const normalizedId = normalizeChangeIdentifier('If5a3ae8cb5a107e187447802358417f311d0c4b1')
// Extract change ID from commit message
const commitMsg = `feat: add feature
Change-Id: If5a3ae8cb5a107e187447802358417f311d0c4b1`
const changeId = extractChangeIdFromCommitMessage(commitMsg)
console.log(changeId) // "If5a3ae8cb5a107e187447802358417f311d0c4b1"
// Extract change number from Gerrit URL
const url = 'https://gerrit.example.com/c/project/+/12345'
const changeNumber = extractChangeNumber(url)
console.log(changeNumber) // "12345"
// Normalize Gerrit host
const host = normalizeGerritHost('gerrit.example.com')
console.log(host) // "https://gerrit.example.com"
```
### 9. Working with Schemas
```typescript
import { Schema } from '@effect/schema'
import { Effect } from 'effect'
import { ChangeInfo, ReviewInput } from '@aaronshaf/ger'
// Validate and decode API responses
const validateChange = (data: unknown) =>
Schema.decodeUnknown(ChangeInfo)(data)
// Validate review input before sending
const validateReview = (review: unknown) =>
Schema.decodeUnknown(ReviewInput)(review)
// Use in an Effect program
const safeReview = Effect.gen(function* () {
const review = {
message: 'LGTM',
labels: { 'Code-Review': 2 },
}
const validated = yield* validateReview(review)
console.log('Review is valid:', validated)
return validated
})
```
## Direct Module Access
You can also import directly from specific modules:
```typescript
// Import from specific services
import { GerritApiService, GerritApiServiceLive } from '@aaronshaf/ger/api'
import { ConfigService, ConfigServiceLive } from '@aaronshaf/ger/services/config'
// Import from specific schemas
import { ChangeInfo, ReviewInput } from '@aaronshaf/ger/schemas/gerrit'
// Import utilities
import { normalizeChangeIdentifier, extractChangeNumber } from '@aaronshaf/ger/utils'
```
## TypeScript Configuration
Make sure your `tsconfig.json` includes:
```json
{
"compilerOptions": {
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"strict": true
}
}
```
## More Information
- See the [main README](./README.md) for CLI usage
- Check out the [Effect documentation](https://effect.website/) to learn more about Effect-TS
- View the type definitions in your IDE for detailed API documentation