@aaronshaf/ger 0.1.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/.eslintrc.js +12 -0
- package/.github/workflows/ci-simple.yml +53 -0
- package/.github/workflows/ci.yml +171 -0
- package/.github/workflows/claude-code-review.yml +78 -0
- package/.github/workflows/claude.yml +64 -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 +103 -0
- package/DEVELOPMENT.md +361 -0
- package/LICENSE +21 -0
- package/README.md +325 -0
- package/bin/ger +3 -0
- package/biome.json +36 -0
- package/bun.lock +688 -0
- package/bunfig.toml +8 -0
- package/oxlint.json +24 -0
- package/package.json +55 -0
- package/scripts/check-coverage.ts +69 -0
- package/scripts/check-file-size.ts +38 -0
- package/scripts/fix-test-mocks.ts +55 -0
- package/src/api/gerrit.ts +466 -0
- package/src/cli/commands/abandon.ts +65 -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/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/review.ts +593 -0
- package/src/cli/commands/setup.ts +230 -0
- package/src/cli/commands/show.ts +303 -0
- package/src/cli/commands/status.ts +35 -0
- package/src/cli/commands/workspace.ts +200 -0
- package/src/cli/index.ts +420 -0
- package/src/prompts/default-review.md +80 -0
- package/src/prompts/system-inline-review.md +88 -0
- package/src/prompts/system-overall-review.md +152 -0
- package/src/schemas/config.test.ts +245 -0
- package/src/schemas/config.ts +75 -0
- package/src/schemas/gerrit.ts +455 -0
- package/src/services/ai-enhanced.ts +167 -0
- package/src/services/ai.ts +182 -0
- package/src/services/config.test.ts +414 -0
- package/src/services/config.ts +206 -0
- package/src/test-utils/mock-generator.ts +73 -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/message-filters.ts +26 -0
- package/src/utils/shell-safety.ts +117 -0
- package/src/utils/status-indicators.ts +100 -0
- package/src/utils/url-parser.test.ts +123 -0
- package/src/utils/url-parser.ts +91 -0
- package/tests/abandon.test.ts +163 -0
- package/tests/ai-service.test.ts +489 -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 +707 -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/helpers/config-mock.ts +27 -0
- package/tests/incoming.test.ts +357 -0
- package/tests/interactive-incoming.test.ts +173 -0
- package/tests/mine.test.ts +318 -0
- package/tests/mocks/fetch-mock.ts +139 -0
- package/tests/mocks/msw-handlers.ts +80 -0
- package/tests/open.test.ts +233 -0
- package/tests/review.test.ts +669 -0
- package/tests/setup.ts +13 -0
- package/tests/show.test.ts +439 -0
- package/tests/unit/schemas/gerrit.test.ts +85 -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/prompt-helpers.test.ts +175 -0
- package/tests/unit/utils/shell-safety.test.ts +230 -0
- package/tests/unit/utils/status-indicators.test.ts +137 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll, afterEach, afterAll } from 'bun:test'
|
|
2
|
+
import { http, HttpResponse } from 'msw'
|
|
3
|
+
import { setupServer } from 'msw/node'
|
|
4
|
+
import { Effect, Layer } from 'effect'
|
|
5
|
+
import { ConfigService } from '@/services/config'
|
|
6
|
+
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
7
|
+
import { commentCommand } from '@/cli/commands/comment'
|
|
8
|
+
import { EventEmitter } from 'node:events'
|
|
9
|
+
|
|
10
|
+
import { createMockConfigService } from './helpers/config-mock'
|
|
11
|
+
// Create a mock process.stdin for testing
|
|
12
|
+
class MockProcessStdin extends EventEmitter {
|
|
13
|
+
isTTY = false
|
|
14
|
+
readable = true
|
|
15
|
+
|
|
16
|
+
emit(event: string, data?: any): boolean {
|
|
17
|
+
if (event === 'data') {
|
|
18
|
+
super.emit('data', Buffer.from(data))
|
|
19
|
+
// Automatically emit 'end' after data
|
|
20
|
+
setTimeout(() => super.emit('end'), 0)
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
return super.emit(event, data)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const server = setupServer()
|
|
28
|
+
|
|
29
|
+
beforeAll(() => server.listen())
|
|
30
|
+
afterEach(() => server.resetHandlers())
|
|
31
|
+
afterAll(() => server.close())
|
|
32
|
+
|
|
33
|
+
describe('comment command - advanced batch features', () => {
|
|
34
|
+
const mockProcessStdin = new MockProcessStdin()
|
|
35
|
+
|
|
36
|
+
test('should handle batch comments with side parameter', async () => {
|
|
37
|
+
const originalStdin = process.stdin
|
|
38
|
+
Object.defineProperty(process, 'stdin', {
|
|
39
|
+
value: mockProcessStdin,
|
|
40
|
+
configurable: true,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
server.use(
|
|
44
|
+
http.get('*/a/changes/:changeId', () => {
|
|
45
|
+
return HttpResponse.text(`)]}'\n{
|
|
46
|
+
"id": "test-project~main~I123abc",
|
|
47
|
+
"_number": 12345,
|
|
48
|
+
"project": "test-project",
|
|
49
|
+
"branch": "main",
|
|
50
|
+
"change_id": "I123abc",
|
|
51
|
+
"subject": "Test change",
|
|
52
|
+
"status": "NEW",
|
|
53
|
+
"created": "2024-01-15 10:00:00.000000000",
|
|
54
|
+
"updated": "2024-01-15 10:00:00.000000000"
|
|
55
|
+
}`)
|
|
56
|
+
}),
|
|
57
|
+
http.post('*/a/changes/:changeId/revisions/current/review', async ({ request }) => {
|
|
58
|
+
const body = (await request.json()) as {
|
|
59
|
+
message?: string
|
|
60
|
+
comments?: Record<string, unknown[]>
|
|
61
|
+
}
|
|
62
|
+
expect(body.comments).toBeDefined()
|
|
63
|
+
|
|
64
|
+
const fileComments = body.comments?.['src/main.js'] as Array<{
|
|
65
|
+
line?: number
|
|
66
|
+
side?: string
|
|
67
|
+
message: string
|
|
68
|
+
}>
|
|
69
|
+
|
|
70
|
+
expect(fileComments?.length).toBe(2)
|
|
71
|
+
expect(fileComments?.[0]).toMatchObject({
|
|
72
|
+
line: 10,
|
|
73
|
+
side: 'PARENT',
|
|
74
|
+
message: 'Why was this removed?',
|
|
75
|
+
})
|
|
76
|
+
expect(fileComments?.[1]).toMatchObject({
|
|
77
|
+
line: 10,
|
|
78
|
+
side: 'REVISION',
|
|
79
|
+
message: 'Good improvement',
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return HttpResponse.json({})
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
87
|
+
|
|
88
|
+
const program = commentCommand('12345', { batch: true }).pipe(
|
|
89
|
+
Effect.provide(GerritApiServiceLive),
|
|
90
|
+
Effect.provide(mockConfigLayer),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// Simulate stdin data with side parameter
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
mockProcessStdin.emit(
|
|
96
|
+
'data',
|
|
97
|
+
JSON.stringify([
|
|
98
|
+
{ file: 'src/main.js', line: 10, message: 'Why was this removed?', side: 'PARENT' },
|
|
99
|
+
{ file: 'src/main.js', line: 10, message: 'Good improvement', side: 'REVISION' },
|
|
100
|
+
]),
|
|
101
|
+
)
|
|
102
|
+
}, 10)
|
|
103
|
+
|
|
104
|
+
await Effect.runPromise(program)
|
|
105
|
+
|
|
106
|
+
// Restore process.stdin
|
|
107
|
+
Object.defineProperty(process, 'stdin', {
|
|
108
|
+
value: originalStdin,
|
|
109
|
+
configurable: true,
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('should handle batch comments with range parameter', async () => {
|
|
114
|
+
const originalStdin = process.stdin
|
|
115
|
+
Object.defineProperty(process, 'stdin', {
|
|
116
|
+
value: mockProcessStdin,
|
|
117
|
+
configurable: true,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
server.use(
|
|
121
|
+
http.get('*/a/changes/:changeId', () => {
|
|
122
|
+
return HttpResponse.text(`)]}'\n{
|
|
123
|
+
"id": "test-project~main~I123abc",
|
|
124
|
+
"_number": 12345,
|
|
125
|
+
"project": "test-project",
|
|
126
|
+
"branch": "main",
|
|
127
|
+
"change_id": "I123abc",
|
|
128
|
+
"subject": "Test change",
|
|
129
|
+
"status": "NEW"
|
|
130
|
+
}`)
|
|
131
|
+
}),
|
|
132
|
+
http.post('*/a/changes/:changeId/revisions/current/review', async ({ request }) => {
|
|
133
|
+
const body = (await request.json()) as {
|
|
134
|
+
comments?: Record<string, unknown[]>
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const fileComments = body.comments?.['src/Calculator.java'] as Array<{
|
|
138
|
+
range?: {
|
|
139
|
+
start_line: number
|
|
140
|
+
end_line: number
|
|
141
|
+
start_character?: number
|
|
142
|
+
end_character?: number
|
|
143
|
+
}
|
|
144
|
+
message: string
|
|
145
|
+
}>
|
|
146
|
+
|
|
147
|
+
expect(fileComments?.length).toBe(3)
|
|
148
|
+
|
|
149
|
+
// Multi-line range comment
|
|
150
|
+
expect(fileComments?.[0]).toMatchObject({
|
|
151
|
+
range: {
|
|
152
|
+
start_line: 50,
|
|
153
|
+
end_line: 55,
|
|
154
|
+
},
|
|
155
|
+
message: 'This block needs refactoring',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// Character-specific range
|
|
159
|
+
expect(fileComments?.[1]).toMatchObject({
|
|
160
|
+
range: {
|
|
161
|
+
start_line: 10,
|
|
162
|
+
start_character: 8,
|
|
163
|
+
end_line: 10,
|
|
164
|
+
end_character: 25,
|
|
165
|
+
},
|
|
166
|
+
message: 'Variable name is confusing',
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Mixed with regular line comment
|
|
170
|
+
expect(fileComments?.[2]).toMatchObject({
|
|
171
|
+
line: 42,
|
|
172
|
+
message: 'Add null check here',
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return HttpResponse.json({})
|
|
176
|
+
}),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
180
|
+
|
|
181
|
+
const program = commentCommand('12345', { batch: true }).pipe(
|
|
182
|
+
Effect.provide(GerritApiServiceLive),
|
|
183
|
+
Effect.provide(mockConfigLayer),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Simulate stdin data with range parameter
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
mockProcessStdin.emit(
|
|
189
|
+
'data',
|
|
190
|
+
JSON.stringify([
|
|
191
|
+
{
|
|
192
|
+
file: 'src/Calculator.java',
|
|
193
|
+
range: { start_line: 50, end_line: 55 },
|
|
194
|
+
message: 'This block needs refactoring',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
file: 'src/Calculator.java',
|
|
198
|
+
range: { start_line: 10, start_character: 8, end_line: 10, end_character: 25 },
|
|
199
|
+
message: 'Variable name is confusing',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
file: 'src/Calculator.java',
|
|
203
|
+
line: 42,
|
|
204
|
+
message: 'Add null check here',
|
|
205
|
+
},
|
|
206
|
+
]),
|
|
207
|
+
)
|
|
208
|
+
}, 10)
|
|
209
|
+
|
|
210
|
+
await Effect.runPromise(program)
|
|
211
|
+
|
|
212
|
+
// Restore process.stdin
|
|
213
|
+
Object.defineProperty(process, 'stdin', {
|
|
214
|
+
value: originalStdin,
|
|
215
|
+
configurable: true,
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('should handle batch comments with both side and range', async () => {
|
|
220
|
+
const originalStdin = process.stdin
|
|
221
|
+
Object.defineProperty(process, 'stdin', {
|
|
222
|
+
value: mockProcessStdin,
|
|
223
|
+
configurable: true,
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
server.use(
|
|
227
|
+
http.get('*/a/changes/:changeId', () => {
|
|
228
|
+
return HttpResponse.text(`)]}'\n{
|
|
229
|
+
"id": "test-project~main~I123abc",
|
|
230
|
+
"_number": 12345,
|
|
231
|
+
"project": "test-project",
|
|
232
|
+
"branch": "main",
|
|
233
|
+
"change_id": "I123abc",
|
|
234
|
+
"subject": "Test change",
|
|
235
|
+
"status": "NEW"
|
|
236
|
+
}`)
|
|
237
|
+
}),
|
|
238
|
+
http.post('*/a/changes/:changeId/revisions/current/review', async ({ request }) => {
|
|
239
|
+
const body = (await request.json()) as {
|
|
240
|
+
comments?: Record<string, unknown[]>
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const fileComments = body.comments?.['src/Service.java'] as Array<{
|
|
244
|
+
range?: {
|
|
245
|
+
start_line: number
|
|
246
|
+
end_line: number
|
|
247
|
+
}
|
|
248
|
+
side?: string
|
|
249
|
+
message: string
|
|
250
|
+
unresolved?: boolean
|
|
251
|
+
}>
|
|
252
|
+
|
|
253
|
+
expect(fileComments?.length).toBe(2)
|
|
254
|
+
|
|
255
|
+
// Range comment on PARENT side
|
|
256
|
+
expect(fileComments?.[0]).toMatchObject({
|
|
257
|
+
range: {
|
|
258
|
+
start_line: 20,
|
|
259
|
+
end_line: 35,
|
|
260
|
+
},
|
|
261
|
+
side: 'PARENT',
|
|
262
|
+
message: 'Why was this error handling removed?',
|
|
263
|
+
unresolved: true,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// Range comment on REVISION side
|
|
267
|
+
expect(fileComments?.[1]).toMatchObject({
|
|
268
|
+
range: {
|
|
269
|
+
start_line: 20,
|
|
270
|
+
end_line: 35,
|
|
271
|
+
},
|
|
272
|
+
side: 'REVISION',
|
|
273
|
+
message: 'New error handling looks good, but consider extracting',
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
return HttpResponse.json({})
|
|
277
|
+
}),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
281
|
+
|
|
282
|
+
const program = commentCommand('12345', { batch: true }).pipe(
|
|
283
|
+
Effect.provide(GerritApiServiceLive),
|
|
284
|
+
Effect.provide(mockConfigLayer),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
// Simulate stdin data with both range and side
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
mockProcessStdin.emit(
|
|
290
|
+
'data',
|
|
291
|
+
JSON.stringify([
|
|
292
|
+
{
|
|
293
|
+
file: 'src/Service.java',
|
|
294
|
+
range: { start_line: 20, end_line: 35 },
|
|
295
|
+
side: 'PARENT',
|
|
296
|
+
message: 'Why was this error handling removed?',
|
|
297
|
+
unresolved: true,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
file: 'src/Service.java',
|
|
301
|
+
range: { start_line: 20, end_line: 35 },
|
|
302
|
+
side: 'REVISION',
|
|
303
|
+
message: 'New error handling looks good, but consider extracting',
|
|
304
|
+
},
|
|
305
|
+
]),
|
|
306
|
+
)
|
|
307
|
+
}, 10)
|
|
308
|
+
|
|
309
|
+
await Effect.runPromise(program)
|
|
310
|
+
|
|
311
|
+
// Restore process.stdin
|
|
312
|
+
Object.defineProperty(process, 'stdin', {
|
|
313
|
+
value: originalStdin,
|
|
314
|
+
configurable: true,
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
test('should validate side parameter values', async () => {
|
|
319
|
+
const originalStdin = process.stdin
|
|
320
|
+
Object.defineProperty(process, 'stdin', {
|
|
321
|
+
value: mockProcessStdin,
|
|
322
|
+
configurable: true,
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
326
|
+
|
|
327
|
+
const program = commentCommand('12345', { batch: true }).pipe(
|
|
328
|
+
Effect.provide(GerritApiServiceLive),
|
|
329
|
+
Effect.provide(mockConfigLayer),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
// Simulate invalid side value
|
|
333
|
+
setTimeout(() => {
|
|
334
|
+
mockProcessStdin.emit(
|
|
335
|
+
'data',
|
|
336
|
+
JSON.stringify([
|
|
337
|
+
{
|
|
338
|
+
file: 'src/main.js',
|
|
339
|
+
line: 10,
|
|
340
|
+
message: 'Test',
|
|
341
|
+
side: 'INVALID', // Invalid side value
|
|
342
|
+
},
|
|
343
|
+
]),
|
|
344
|
+
)
|
|
345
|
+
}, 10)
|
|
346
|
+
|
|
347
|
+
await expect(Effect.runPromise(program)).rejects.toThrow('Invalid batch input format')
|
|
348
|
+
|
|
349
|
+
// Restore process.stdin
|
|
350
|
+
Object.defineProperty(process, 'stdin', {
|
|
351
|
+
value: originalStdin,
|
|
352
|
+
configurable: true,
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
test('should require either line or range but not both', async () => {
|
|
357
|
+
const originalStdin = process.stdin
|
|
358
|
+
Object.defineProperty(process, 'stdin', {
|
|
359
|
+
value: mockProcessStdin,
|
|
360
|
+
configurable: true,
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
server.use(
|
|
364
|
+
http.get('*/a/changes/:changeId', () => {
|
|
365
|
+
return HttpResponse.text(`)]}'\n{
|
|
366
|
+
"id": "test-project~main~I123abc",
|
|
367
|
+
"_number": 12345,
|
|
368
|
+
"project": "test-project",
|
|
369
|
+
"branch": "main",
|
|
370
|
+
"change_id": "I123abc",
|
|
371
|
+
"subject": "Test change",
|
|
372
|
+
"status": "NEW"
|
|
373
|
+
}`)
|
|
374
|
+
}),
|
|
375
|
+
http.post('*/a/changes/:changeId/revisions/current/review', async ({ request }) => {
|
|
376
|
+
const body = (await request.json()) as {
|
|
377
|
+
comments?: Record<string, unknown[]>
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const fileComments = body.comments?.['src/main.js'] as Array<{
|
|
381
|
+
line?: number
|
|
382
|
+
range?: unknown
|
|
383
|
+
message: string
|
|
384
|
+
}>
|
|
385
|
+
|
|
386
|
+
// Should use range when both are provided (range takes precedence)
|
|
387
|
+
expect(fileComments?.[0]).toMatchObject({
|
|
388
|
+
range: {
|
|
389
|
+
start_line: 10,
|
|
390
|
+
end_line: 15,
|
|
391
|
+
},
|
|
392
|
+
message: 'Test comment',
|
|
393
|
+
})
|
|
394
|
+
// line should NOT be included when range is present (Gerrit API preference)
|
|
395
|
+
expect(fileComments?.[0].line).toBeUndefined()
|
|
396
|
+
|
|
397
|
+
return HttpResponse.json({})
|
|
398
|
+
}),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
const mockConfigLayer = Layer.succeed(ConfigService, createMockConfigService())
|
|
402
|
+
|
|
403
|
+
const program = commentCommand('12345', { batch: true }).pipe(
|
|
404
|
+
Effect.provide(GerritApiServiceLive),
|
|
405
|
+
Effect.provide(mockConfigLayer),
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
// Both line and range provided - should work
|
|
409
|
+
setTimeout(() => {
|
|
410
|
+
mockProcessStdin.emit(
|
|
411
|
+
'data',
|
|
412
|
+
JSON.stringify([
|
|
413
|
+
{
|
|
414
|
+
file: 'src/main.js',
|
|
415
|
+
line: 10, // Will be included
|
|
416
|
+
range: { start_line: 10, end_line: 15 }, // Takes precedence
|
|
417
|
+
message: 'Test comment',
|
|
418
|
+
},
|
|
419
|
+
]),
|
|
420
|
+
)
|
|
421
|
+
}, 10)
|
|
422
|
+
|
|
423
|
+
await Effect.runPromise(program)
|
|
424
|
+
|
|
425
|
+
// Restore process.stdin
|
|
426
|
+
Object.defineProperty(process, 'stdin', {
|
|
427
|
+
value: originalStdin,
|
|
428
|
+
configurable: true,
|
|
429
|
+
})
|
|
430
|
+
})
|
|
431
|
+
})
|