@aaronshaf/ger 1.2.10 → 2.0.0
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/.ast-grep/rules/no-as-casting.yml +13 -0
- package/.claude-plugin/plugin.json +22 -0
- package/.github/workflows/ci-simple.yml +53 -0
- package/.github/workflows/ci.yml +171 -0
- package/.github/workflows/claude-code-review.yml +83 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/dependency-update.yml +84 -0
- package/.github/workflows/release.yml +166 -0
- package/.github/workflows/security-scan.yml +113 -0
- package/.github/workflows/security.yml +96 -0
- package/.husky/pre-commit +16 -0
- package/.husky/pre-push +25 -0
- package/.lintstagedrc.json +6 -0
- package/.tool-versions +1 -0
- package/CLAUDE.md +105 -0
- package/DEVELOPMENT.md +361 -0
- package/EXAMPLES.md +457 -0
- package/README.md +831 -16
- package/bin/ger +3 -18
- package/biome.json +36 -0
- package/bun.lock +678 -0
- package/bunfig.toml +8 -0
- package/docs/adr/0001-use-effect-for-side-effects.md +65 -0
- package/docs/adr/0002-use-bun-runtime.md +64 -0
- package/docs/adr/0003-store-credentials-in-home-directory.md +75 -0
- package/docs/adr/0004-use-commander-for-cli.md +76 -0
- package/docs/adr/0005-use-effect-schema-for-validation.md +93 -0
- package/docs/adr/0006-use-msw-for-api-mocking.md +89 -0
- package/docs/adr/0007-git-hooks-for-quality.md +94 -0
- package/docs/adr/0008-no-as-typecasting.md +83 -0
- package/docs/adr/0009-file-size-limits.md +82 -0
- package/docs/adr/0010-llm-friendly-xml-output.md +93 -0
- package/docs/adr/0011-ai-tool-strategy-pattern.md +102 -0
- package/docs/adr/0012-build-status-message-parsing.md +94 -0
- package/docs/adr/0013-git-subprocess-integration.md +98 -0
- package/docs/adr/0014-group-management-support.md +95 -0
- package/docs/adr/0015-batch-comment-processing.md +111 -0
- package/docs/adr/0016-flexible-change-identifiers.md +94 -0
- package/docs/adr/0017-git-worktree-support.md +102 -0
- package/docs/adr/0018-auto-install-commit-hook.md +103 -0
- package/docs/adr/0019-sdk-package-exports.md +95 -0
- package/docs/adr/0020-code-coverage-enforcement.md +105 -0
- package/docs/adr/0021-typescript-isolated-declarations.md +83 -0
- package/docs/adr/0022-biome-oxlint-tooling.md +124 -0
- package/docs/adr/README.md +30 -0
- package/docs/prd/README.md +12 -0
- package/docs/prd/architecture.md +325 -0
- package/docs/prd/commands.md +425 -0
- package/docs/prd/data-model.md +349 -0
- package/docs/prd/overview.md +124 -0
- package/index.ts +219 -0
- package/oxlint.json +24 -0
- package/package.json +82 -15
- package/scripts/check-coverage.ts +69 -0
- package/scripts/check-file-size.ts +38 -0
- package/scripts/fix-test-mocks.ts +55 -0
- package/skills/gerrit-workflow/SKILL.md +247 -0
- package/skills/gerrit-workflow/examples.md +572 -0
- package/skills/gerrit-workflow/reference.md +728 -0
- package/src/api/gerrit.ts +696 -0
- package/src/cli/commands/abandon.ts +65 -0
- package/src/cli/commands/add-reviewer.ts +156 -0
- package/src/cli/commands/build-status.ts +282 -0
- package/src/cli/commands/checkout.ts +422 -0
- package/src/cli/commands/comment.ts +460 -0
- package/src/cli/commands/comments.ts +85 -0
- package/src/cli/commands/diff.ts +71 -0
- package/src/cli/commands/extract-url.ts +266 -0
- package/src/cli/commands/groups-members.ts +104 -0
- package/src/cli/commands/groups-show.ts +169 -0
- package/src/cli/commands/groups.ts +137 -0
- package/src/cli/commands/incoming.ts +226 -0
- package/src/cli/commands/init.ts +164 -0
- package/src/cli/commands/mine.ts +115 -0
- package/src/cli/commands/open.ts +57 -0
- package/src/cli/commands/projects.ts +68 -0
- package/src/cli/commands/push.ts +430 -0
- package/src/cli/commands/rebase.ts +52 -0
- package/src/cli/commands/remove-reviewer.ts +123 -0
- package/src/cli/commands/restore.ts +50 -0
- package/src/cli/commands/review.ts +486 -0
- package/src/cli/commands/search.ts +162 -0
- package/src/cli/commands/setup.ts +286 -0
- package/src/cli/commands/show.ts +491 -0
- package/src/cli/commands/status.ts +35 -0
- package/src/cli/commands/submit.ts +108 -0
- package/src/cli/commands/vote.ts +119 -0
- package/src/cli/commands/workspace.ts +200 -0
- package/src/cli/index.ts +53 -0
- package/src/cli/register-commands.ts +659 -0
- package/src/cli/register-group-commands.ts +88 -0
- package/src/cli/register-reviewer-commands.ts +97 -0
- package/src/prompts/default-review.md +86 -0
- package/src/prompts/system-inline-review.md +135 -0
- package/src/prompts/system-overall-review.md +206 -0
- package/src/schemas/config.test.ts +245 -0
- package/src/schemas/config.ts +84 -0
- package/src/schemas/gerrit.ts +681 -0
- package/src/services/commit-hook.ts +314 -0
- package/src/services/config.test.ts +150 -0
- package/src/services/config.ts +250 -0
- package/src/services/git-worktree.ts +342 -0
- package/src/services/review-strategy.ts +292 -0
- package/src/test-utils/mock-generator.ts +138 -0
- package/src/utils/change-id.test.ts +98 -0
- package/src/utils/change-id.ts +63 -0
- package/src/utils/comment-formatters.ts +153 -0
- package/src/utils/diff-context.ts +103 -0
- package/src/utils/diff-formatters.ts +141 -0
- package/src/utils/formatters.ts +85 -0
- package/src/utils/git-commit.test.ts +277 -0
- package/src/utils/git-commit.ts +122 -0
- package/src/utils/index.ts +55 -0
- package/src/utils/message-filters.ts +26 -0
- package/src/utils/review-formatters.ts +89 -0
- package/src/utils/review-prompt-builder.ts +110 -0
- package/src/utils/shell-safety.ts +117 -0
- package/src/utils/status-indicators.ts +100 -0
- package/src/utils/url-parser.test.ts +271 -0
- package/src/utils/url-parser.ts +118 -0
- package/tests/abandon.test.ts +230 -0
- package/tests/add-reviewer.test.ts +579 -0
- package/tests/build-status-watch.test.ts +344 -0
- package/tests/build-status.test.ts +789 -0
- package/tests/change-id-formats.test.ts +268 -0
- package/tests/checkout/integration.test.ts +653 -0
- package/tests/checkout/parse-input.test.ts +55 -0
- package/tests/checkout/validation.test.ts +178 -0
- package/tests/comment-batch-advanced.test.ts +431 -0
- package/tests/comment-gerrit-api-compliance.test.ts +414 -0
- package/tests/comment.test.ts +708 -0
- package/tests/comments.test.ts +323 -0
- package/tests/config-service-simple.test.ts +100 -0
- package/tests/diff.test.ts +419 -0
- package/tests/extract-url.test.ts +517 -0
- package/tests/groups-members.test.ts +256 -0
- package/tests/groups-show.test.ts +323 -0
- package/tests/groups.test.ts +334 -0
- package/tests/helpers/build-status-test-setup.ts +83 -0
- package/tests/helpers/config-mock.ts +27 -0
- package/tests/incoming.test.ts +357 -0
- package/tests/init.test.ts +70 -0
- package/tests/integration/commit-hook.test.ts +246 -0
- package/tests/interactive-incoming.test.ts +173 -0
- package/tests/mine.test.ts +285 -0
- package/tests/mocks/msw-handlers.ts +80 -0
- package/tests/open.test.ts +233 -0
- package/tests/projects.test.ts +259 -0
- package/tests/rebase.test.ts +271 -0
- package/tests/remove-reviewer.test.ts +357 -0
- package/tests/restore.test.ts +237 -0
- package/tests/review.test.ts +135 -0
- package/tests/search.test.ts +712 -0
- package/tests/setup.test.ts +63 -0
- package/tests/show-auto-detect.test.ts +324 -0
- package/tests/show.test.ts +813 -0
- package/tests/status.test.ts +145 -0
- package/tests/submit.test.ts +316 -0
- package/tests/unit/commands/push.test.ts +194 -0
- package/tests/unit/git-branch-detection.test.ts +82 -0
- package/tests/unit/git-worktree.test.ts +55 -0
- package/tests/unit/patterns/push-patterns.test.ts +148 -0
- package/tests/unit/schemas/gerrit.test.ts +85 -0
- package/tests/unit/services/commit-hook.test.ts +132 -0
- package/tests/unit/services/review-strategy.test.ts +349 -0
- package/tests/unit/test-utils/mock-generator.test.ts +154 -0
- package/tests/unit/utils/comment-formatters.test.ts +415 -0
- package/tests/unit/utils/diff-context.test.ts +171 -0
- package/tests/unit/utils/diff-formatters.test.ts +165 -0
- package/tests/unit/utils/formatters.test.ts +411 -0
- package/tests/unit/utils/message-filters.test.ts +227 -0
- package/tests/unit/utils/shell-safety.test.ts +230 -0
- package/tests/unit/utils/status-indicators.test.ts +137 -0
- package/tests/vote.test.ts +317 -0
- package/tests/workspace.test.ts +295 -0
- package/tsconfig.json +36 -5
- package/src/commands/branch.ts +0 -180
- package/src/ger.ts +0 -22
- package/src/types.d.ts +0 -35
- package/src/utils.ts +0 -130
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, mock } from 'bun:test'
|
|
2
|
+
import { Effect, Layer } from 'effect'
|
|
3
|
+
import { HttpResponse, http } from 'msw'
|
|
4
|
+
import { setupServer } from 'msw/node'
|
|
5
|
+
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
6
|
+
import { abandonCommand } from '@/cli/commands/abandon'
|
|
7
|
+
import { ConfigService } from '@/services/config'
|
|
8
|
+
import { createMockConfigService } from './helpers/config-mock'
|
|
9
|
+
import type { ChangeInfo } from '@/schemas/gerrit'
|
|
10
|
+
|
|
11
|
+
const mockChange: ChangeInfo = {
|
|
12
|
+
id: 'test-project~master~I123',
|
|
13
|
+
_number: 12345,
|
|
14
|
+
change_id: 'I123',
|
|
15
|
+
project: 'test-project',
|
|
16
|
+
branch: 'master',
|
|
17
|
+
subject: 'Test change to abandon',
|
|
18
|
+
status: 'NEW',
|
|
19
|
+
created: '2024-01-01 10:00:00.000000000',
|
|
20
|
+
updated: '2024-01-01 12:00:00.000000000',
|
|
21
|
+
owner: {
|
|
22
|
+
_account_id: 1000,
|
|
23
|
+
name: 'Test User',
|
|
24
|
+
email: 'test@example.com',
|
|
25
|
+
},
|
|
26
|
+
labels: {
|
|
27
|
+
'Code-Review': {
|
|
28
|
+
value: 0,
|
|
29
|
+
},
|
|
30
|
+
Verified: {
|
|
31
|
+
value: 0,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
work_in_progress: false,
|
|
35
|
+
submittable: false,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create MSW server
|
|
39
|
+
const server = setupServer(
|
|
40
|
+
// Default handler for auth check
|
|
41
|
+
http.get('*/a/accounts/self', ({ request }) => {
|
|
42
|
+
const auth = request.headers.get('Authorization')
|
|
43
|
+
if (!auth || !auth.startsWith('Basic ')) {
|
|
44
|
+
return HttpResponse.text('Unauthorized', { status: 401 })
|
|
45
|
+
}
|
|
46
|
+
return HttpResponse.json({
|
|
47
|
+
_account_id: 1000,
|
|
48
|
+
name: 'Test User',
|
|
49
|
+
email: 'test@example.com',
|
|
50
|
+
})
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
describe('abandon command', () => {
|
|
55
|
+
let mockConsoleLog: ReturnType<typeof mock>
|
|
56
|
+
let mockConsoleError: ReturnType<typeof mock>
|
|
57
|
+
|
|
58
|
+
beforeAll(() => {
|
|
59
|
+
server.listen({ onUnhandledRequest: 'bypass' })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
server.close()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
mockConsoleLog = mock(() => {})
|
|
68
|
+
mockConsoleError = mock(() => {})
|
|
69
|
+
console.log = mockConsoleLog
|
|
70
|
+
console.error = mockConsoleError
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
server.resetHandlers()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should abandon a change with a message', async () => {
|
|
78
|
+
server.use(
|
|
79
|
+
http.get('*/a/changes/12345', () => {
|
|
80
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
81
|
+
}),
|
|
82
|
+
http.post('*/a/changes/12345/abandon', async ({ request }) => {
|
|
83
|
+
const body = (await request.json()) as { message?: string }
|
|
84
|
+
expect(body.message).toBe('No longer needed')
|
|
85
|
+
return HttpResponse.text(")]}'\n{}")
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
90
|
+
const program = abandonCommand('12345', {
|
|
91
|
+
message: 'No longer needed',
|
|
92
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer))
|
|
93
|
+
|
|
94
|
+
await Effect.runPromise(program)
|
|
95
|
+
|
|
96
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
97
|
+
expect(output).toContain('Abandoned change 12345')
|
|
98
|
+
expect(output).toContain('Test change to abandon')
|
|
99
|
+
expect(output).toContain('Message: No longer needed')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should abandon a change without a message', async () => {
|
|
103
|
+
server.use(
|
|
104
|
+
http.get('*/a/changes/12345', () => {
|
|
105
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
106
|
+
}),
|
|
107
|
+
http.post('*/a/changes/12345/abandon', async ({ request }) => {
|
|
108
|
+
const body = (await request.json()) as { message?: string }
|
|
109
|
+
expect(body.message).toBeUndefined()
|
|
110
|
+
return HttpResponse.text(")]}'\n{}")
|
|
111
|
+
}),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
115
|
+
const program = abandonCommand('12345', {}).pipe(
|
|
116
|
+
Effect.provide(GerritApiServiceLive),
|
|
117
|
+
Effect.provide(mockConfigLayer),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
await Effect.runPromise(program)
|
|
121
|
+
|
|
122
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
123
|
+
expect(output).toContain('Abandoned change 12345')
|
|
124
|
+
expect(output).toContain('Test change to abandon')
|
|
125
|
+
expect(output).not.toContain('Message:')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('should output XML format when --xml flag is used', async () => {
|
|
129
|
+
server.use(
|
|
130
|
+
http.get('*/a/changes/12345', () => {
|
|
131
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
132
|
+
}),
|
|
133
|
+
http.post('*/a/changes/12345/abandon', async ({ request }) => {
|
|
134
|
+
const body = (await request.json()) as { message?: string }
|
|
135
|
+
expect(body.message).toBe('Abandoning for testing')
|
|
136
|
+
return HttpResponse.text(")]}'\n{}")
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
141
|
+
const program = abandonCommand('12345', {
|
|
142
|
+
xml: true,
|
|
143
|
+
message: 'Abandoning for testing',
|
|
144
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer))
|
|
145
|
+
|
|
146
|
+
await Effect.runPromise(program)
|
|
147
|
+
|
|
148
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
149
|
+
expect(output).toContain('<?xml version="1.0" encoding="UTF-8"?>')
|
|
150
|
+
expect(output).toContain('<abandon_result>')
|
|
151
|
+
expect(output).toContain('<status>success</status>')
|
|
152
|
+
expect(output).toContain('<change_number>12345</change_number>')
|
|
153
|
+
expect(output).toContain('<subject><![CDATA[Test change to abandon]]></subject>')
|
|
154
|
+
expect(output).toContain('<message><![CDATA[Abandoning for testing]]></message>')
|
|
155
|
+
expect(output).toContain('</abandon_result>')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should output XML format without message when no message provided', async () => {
|
|
159
|
+
server.use(
|
|
160
|
+
http.get('*/a/changes/12345', () => {
|
|
161
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
162
|
+
}),
|
|
163
|
+
http.post('*/a/changes/12345/abandon', async () => {
|
|
164
|
+
return HttpResponse.text(")]}'\n{}")
|
|
165
|
+
}),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
169
|
+
const program = abandonCommand('12345', { xml: true }).pipe(
|
|
170
|
+
Effect.provide(GerritApiServiceLive),
|
|
171
|
+
Effect.provide(mockConfigLayer),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
await Effect.runPromise(program)
|
|
175
|
+
|
|
176
|
+
const output = mockConsoleLog.mock.calls.map((call) => call[0]).join('\n')
|
|
177
|
+
expect(output).toContain('<abandon_result>')
|
|
178
|
+
expect(output).toContain('<status>success</status>')
|
|
179
|
+
expect(output).not.toContain('<message>')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should handle not found errors gracefully', async () => {
|
|
183
|
+
server.use(
|
|
184
|
+
http.get('*/a/changes/99999', () => {
|
|
185
|
+
return HttpResponse.text('Change not found', { status: 404 })
|
|
186
|
+
}),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
190
|
+
const program = abandonCommand('99999', {
|
|
191
|
+
message: 'Test message',
|
|
192
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer))
|
|
193
|
+
|
|
194
|
+
// Should fail when change is not found
|
|
195
|
+
await expect(Effect.runPromise(program)).rejects.toThrow()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should show error when change ID is not provided', async () => {
|
|
199
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
200
|
+
const program = abandonCommand(undefined, {}).pipe(
|
|
201
|
+
Effect.provide(GerritApiServiceLive),
|
|
202
|
+
Effect.provide(mockConfigLayer),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
await Effect.runPromise(program)
|
|
206
|
+
|
|
207
|
+
const errorOutput = mockConsoleError.mock.calls.map((call) => call[0]).join('\n')
|
|
208
|
+
expect(errorOutput).toContain('Change ID is required')
|
|
209
|
+
expect(errorOutput).toContain('Usage: ger abandon <change-id>')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should handle abandon API failure', async () => {
|
|
213
|
+
server.use(
|
|
214
|
+
http.get('*/a/changes/12345', () => {
|
|
215
|
+
return HttpResponse.text(`)]}'\n${JSON.stringify(mockChange)}`)
|
|
216
|
+
}),
|
|
217
|
+
http.post('*/a/changes/12345/abandon', () => {
|
|
218
|
+
return HttpResponse.text('Forbidden', { status: 403 })
|
|
219
|
+
}),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
223
|
+
const program = abandonCommand('12345', {
|
|
224
|
+
message: 'Test',
|
|
225
|
+
}).pipe(Effect.provide(GerritApiServiceLive), Effect.provide(mockConfigLayer))
|
|
226
|
+
|
|
227
|
+
// Should throw/fail
|
|
228
|
+
await expect(Effect.runPromise(program)).rejects.toThrow()
|
|
229
|
+
})
|
|
230
|
+
})
|