@blogic-cz/agent-tools 0.14.41 → 0.14.42
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/package.json +1 -1
- package/src/gh-tool/index.ts +2 -0
- package/src/gh-tool/pr/commands.ts +93 -9
- package/src/gh-tool/pr/core.ts +36 -1
- package/src/gh-tool/pr/index.ts +1 -0
package/package.json
CHANGED
package/src/gh-tool/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
prReplyAndResolveCommand,
|
|
41
41
|
prReviewTriageBatchCommand,
|
|
42
42
|
prReviewTriageCommand,
|
|
43
|
+
prWaitMergeableCommand,
|
|
43
44
|
} from "./pr/index";
|
|
44
45
|
import { branchRenameCommand } from "./branch";
|
|
45
46
|
import {
|
|
@@ -74,6 +75,7 @@ const prCommand = Command.make("pr", {}).pipe(
|
|
|
74
75
|
prCloseCommand,
|
|
75
76
|
prEditCommand,
|
|
76
77
|
prMergeCommand,
|
|
78
|
+
prWaitMergeableCommand,
|
|
77
79
|
prThreadsCommand,
|
|
78
80
|
prCommentsCommand,
|
|
79
81
|
prIssueCommentsCommand,
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { Command, Flag } from "effect/unstable/cli";
|
|
2
|
-
import { Effect, Option } from "effect";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
3
|
|
|
4
4
|
import type { CheckResult, PRStatusResult } from "#gh/types";
|
|
5
5
|
|
|
6
6
|
import { formatOption, logFormatted } from "#shared";
|
|
7
7
|
import { GitHubService } from "#gh/service";
|
|
8
|
+
import { GitHubCommandError } from "#gh/errors";
|
|
9
|
+
|
|
10
|
+
const emptyBatchError = (batch: string) =>
|
|
11
|
+
new GitHubCommandError({
|
|
12
|
+
message: `--prs received no valid PR numbers: ${JSON.stringify(batch)}`,
|
|
13
|
+
command: "gh pr --prs",
|
|
14
|
+
exitCode: 1,
|
|
15
|
+
stderr: "",
|
|
16
|
+
hint: "Pass comma-separated positive integers, e.g. --prs 1,2,3.",
|
|
17
|
+
});
|
|
8
18
|
import {
|
|
9
19
|
resolveDefaultTextInput,
|
|
10
20
|
resolveOptionalTextInput,
|
|
@@ -29,6 +39,7 @@ import {
|
|
|
29
39
|
mergePR,
|
|
30
40
|
rerunChecks,
|
|
31
41
|
viewPR,
|
|
42
|
+
waitForMergeable,
|
|
32
43
|
} from "./core";
|
|
33
44
|
import {
|
|
34
45
|
fetchComments,
|
|
@@ -127,18 +138,34 @@ export const prViewCommand = Command.make(
|
|
|
127
138
|
Flag.withDescription("PR number (default: current branch PR)"),
|
|
128
139
|
Flag.optional,
|
|
129
140
|
),
|
|
141
|
+
prs: Flag.string("prs").pipe(
|
|
142
|
+
Flag.withDescription("Comma-separated PR numbers to view in one call (overrides --pr)"),
|
|
143
|
+
Flag.optional,
|
|
144
|
+
),
|
|
130
145
|
repo: repoOption,
|
|
131
146
|
},
|
|
132
|
-
({ format, pr, repo }) =>
|
|
147
|
+
({ format, pr, prs, repo }) =>
|
|
133
148
|
withRepo(
|
|
134
149
|
repo,
|
|
135
150
|
Effect.gen(function* () {
|
|
136
|
-
const
|
|
137
|
-
|
|
151
|
+
const batch = Option.getOrNull(prs);
|
|
152
|
+
if (batch !== null) {
|
|
153
|
+
const numbers = parsePrNumbers(batch);
|
|
154
|
+
if (numbers.length === 0) return yield* emptyBatchError(batch);
|
|
155
|
+
const results = yield* Effect.all(
|
|
156
|
+
numbers.map((n) => viewPR(n).pipe(Effect.map((info) => ({ pr: n, info })))),
|
|
157
|
+
{ concurrency: 5 },
|
|
158
|
+
);
|
|
159
|
+
yield* logFormatted({ count: results.length, prs: results }, format);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const info = yield* viewPR(Option.getOrNull(pr));
|
|
138
163
|
yield* logFormatted(info, format);
|
|
139
164
|
}),
|
|
140
165
|
),
|
|
141
|
-
).pipe(
|
|
166
|
+
).pipe(
|
|
167
|
+
Command.withDescription("View PR information (use --prs 1,2,3 to view several in one call)"),
|
|
168
|
+
);
|
|
142
169
|
|
|
143
170
|
export const prStatusCommand = Command.make(
|
|
144
171
|
"status",
|
|
@@ -200,6 +227,36 @@ export const prListCommand = Command.make(
|
|
|
200
227
|
),
|
|
201
228
|
);
|
|
202
229
|
|
|
230
|
+
export const prWaitMergeableCommand = Command.make(
|
|
231
|
+
"wait-mergeable",
|
|
232
|
+
{
|
|
233
|
+
format: formatOption,
|
|
234
|
+
pr: Flag.integer("pr").pipe(
|
|
235
|
+
Flag.withDescription("PR number (default: current branch PR)"),
|
|
236
|
+
Flag.optional,
|
|
237
|
+
),
|
|
238
|
+
timeout: Flag.integer("timeout").pipe(
|
|
239
|
+
Flag.withDescription(
|
|
240
|
+
"Max seconds to wait for a definitive mergeable verdict (capped at 180)",
|
|
241
|
+
),
|
|
242
|
+
Flag.withDefault(60),
|
|
243
|
+
),
|
|
244
|
+
repo: repoOption,
|
|
245
|
+
},
|
|
246
|
+
({ format, pr, timeout, repo }) =>
|
|
247
|
+
withRepo(
|
|
248
|
+
repo,
|
|
249
|
+
Effect.gen(function* () {
|
|
250
|
+
const info = yield* waitForMergeable(Option.getOrNull(pr), timeout);
|
|
251
|
+
yield* logFormatted(info, format);
|
|
252
|
+
}),
|
|
253
|
+
),
|
|
254
|
+
).pipe(
|
|
255
|
+
Command.withDescription(
|
|
256
|
+
"Poll until GitHub reports a definitive mergeable verdict (MERGEABLE/CONFLICTING) or timeout",
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
|
|
203
260
|
export const prCreateCommand = Command.make(
|
|
204
261
|
"create",
|
|
205
262
|
{
|
|
@@ -388,6 +445,10 @@ export const prChecksCommand = Command.make(
|
|
|
388
445
|
Flag.withDescription("PR number (default: current branch PR)"),
|
|
389
446
|
Flag.optional,
|
|
390
447
|
),
|
|
448
|
+
prs: Flag.string("prs").pipe(
|
|
449
|
+
Flag.withDescription("Comma-separated PR numbers for a one-shot batch snapshot (no --watch)"),
|
|
450
|
+
Flag.optional,
|
|
451
|
+
),
|
|
391
452
|
repo: repoOption,
|
|
392
453
|
timeout: Flag.integer("timeout").pipe(
|
|
393
454
|
Flag.withDefault(CI_CHECK_WATCH_TIMEOUT_MS / 1000),
|
|
@@ -398,16 +459,39 @@ export const prChecksCommand = Command.make(
|
|
|
398
459
|
Flag.withDescription("Watch until checks complete or timeout"),
|
|
399
460
|
),
|
|
400
461
|
},
|
|
401
|
-
({ failFast, format, pr, repo, timeout, watch }) =>
|
|
462
|
+
({ failFast, format, pr, prs, repo, timeout, watch }) =>
|
|
402
463
|
withRepo(
|
|
403
464
|
repo,
|
|
404
465
|
Effect.gen(function* () {
|
|
405
|
-
const
|
|
406
|
-
|
|
466
|
+
const batch = Option.getOrNull(prs);
|
|
467
|
+
if (batch !== null) {
|
|
468
|
+
if (watch) {
|
|
469
|
+
yield* Console.warn(
|
|
470
|
+
"ℹ️ --watch is ignored with --prs; batch mode returns a one-shot snapshot per PR.",
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const numbers = parsePrNumbers(batch);
|
|
474
|
+
if (numbers.length === 0) return yield* emptyBatchError(batch);
|
|
475
|
+
const results = yield* Effect.all(
|
|
476
|
+
numbers.map((n) =>
|
|
477
|
+
fetchChecks(n, false, failFast, timeout).pipe(
|
|
478
|
+
Effect.map((checks) => ({ pr: n, checks })),
|
|
479
|
+
),
|
|
480
|
+
),
|
|
481
|
+
{ concurrency: 5 },
|
|
482
|
+
);
|
|
483
|
+
yield* logFormatted({ count: results.length, prs: results }, format);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const checks = yield* fetchChecksForCommand(Option.getOrNull(pr), watch, failFast, timeout);
|
|
407
487
|
yield* logFormatted(checks, format);
|
|
408
488
|
}),
|
|
409
489
|
),
|
|
410
|
-
).pipe(
|
|
490
|
+
).pipe(
|
|
491
|
+
Command.withDescription(
|
|
492
|
+
"Fetch CI check status for a PR (--watch to block; --prs 1,2,3 for a batch snapshot)",
|
|
493
|
+
),
|
|
494
|
+
);
|
|
411
495
|
|
|
412
496
|
export const prChecksFailedCommand = Command.make(
|
|
413
497
|
"checks-failed",
|
package/src/gh-tool/pr/core.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Console, Effect, Option, Result } from "effect";
|
|
1
|
+
import { Clock, Console, Duration, Effect, Option, Result } from "effect";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
4
|
BranchPRDetail,
|
|
@@ -482,6 +482,41 @@ export const listPRs = Effect.fn("pr.listPRs")(function* (opts: {
|
|
|
482
482
|
return yield* gh.runGhJson<PRInfo[]>(args);
|
|
483
483
|
});
|
|
484
484
|
|
|
485
|
+
// GitHub recomputes mergeability asynchronously after CI; poll until it settles out of "UNKNOWN".
|
|
486
|
+
const MAX_MERGEABLE_WAIT_SECONDS = 180;
|
|
487
|
+
const MERGEABLE_POLL_INTERVAL_MS = 3000;
|
|
488
|
+
|
|
489
|
+
export const waitForMergeable = Effect.fn("pr.waitForMergeable")(function* (
|
|
490
|
+
pr: number | null,
|
|
491
|
+
timeoutSeconds: number,
|
|
492
|
+
) {
|
|
493
|
+
const cappedSeconds = Math.min(timeoutSeconds, MAX_MERGEABLE_WAIT_SECONDS);
|
|
494
|
+
const start = yield* Clock.currentTimeMillis;
|
|
495
|
+
const deadlineMs = Number(start) + cappedSeconds * 1000;
|
|
496
|
+
|
|
497
|
+
// Effect.whileLoop (not recursion) so TestClock.adjust can advance Effect.sleep without real waits.
|
|
498
|
+
let latest = yield* viewPR(pr);
|
|
499
|
+
let timedOut = false;
|
|
500
|
+
yield* Effect.whileLoop({
|
|
501
|
+
while: () => latest.mergeable === "UNKNOWN" && !timedOut,
|
|
502
|
+
body: () =>
|
|
503
|
+
Effect.gen(function* () {
|
|
504
|
+
const now = yield* Clock.currentTimeMillis;
|
|
505
|
+
if (Number(now) >= deadlineMs) {
|
|
506
|
+
timedOut = true;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
// Cap the sleep to the remaining budget so the total wait doesn't overshoot the deadline.
|
|
510
|
+
const remaining = deadlineMs - Number(now);
|
|
511
|
+
yield* Effect.sleep(Duration.millis(Math.min(MERGEABLE_POLL_INTERVAL_MS, remaining)));
|
|
512
|
+
latest = yield* viewPR(pr);
|
|
513
|
+
}),
|
|
514
|
+
step: () => undefined,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
return latest;
|
|
518
|
+
});
|
|
519
|
+
|
|
485
520
|
export const createPR = Effect.fn("pr.createPR")(function* (opts: {
|
|
486
521
|
base: string | null;
|
|
487
522
|
title: string;
|