@blogic-cz/agent-tools 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/LICENSE +21 -0
- package/README.md +236 -0
- package/package.json +70 -0
- package/schemas/agent-tools.schema.json +319 -0
- package/src/az-tool/build.ts +295 -0
- package/src/az-tool/config.ts +33 -0
- package/src/az-tool/errors.ts +26 -0
- package/src/az-tool/extract-option-value.ts +12 -0
- package/src/az-tool/index.ts +181 -0
- package/src/az-tool/security.ts +130 -0
- package/src/az-tool/service.ts +292 -0
- package/src/az-tool/types.ts +67 -0
- package/src/config/index.ts +12 -0
- package/src/config/loader.ts +170 -0
- package/src/config/types.ts +82 -0
- package/src/credential-guard/claude-hook.ts +28 -0
- package/src/credential-guard/index.ts +435 -0
- package/src/db-tool/config-service.ts +38 -0
- package/src/db-tool/errors.ts +40 -0
- package/src/db-tool/index.ts +91 -0
- package/src/db-tool/schema.ts +69 -0
- package/src/db-tool/security.ts +116 -0
- package/src/db-tool/service.ts +605 -0
- package/src/db-tool/types.ts +33 -0
- package/src/gh-tool/config.ts +7 -0
- package/src/gh-tool/errors.ts +47 -0
- package/src/gh-tool/index.ts +140 -0
- package/src/gh-tool/issue.ts +361 -0
- package/src/gh-tool/pr/commands.ts +432 -0
- package/src/gh-tool/pr/core.ts +497 -0
- package/src/gh-tool/pr/helpers.ts +84 -0
- package/src/gh-tool/pr/index.ts +19 -0
- package/src/gh-tool/pr/review.ts +571 -0
- package/src/gh-tool/repo.ts +147 -0
- package/src/gh-tool/service.ts +192 -0
- package/src/gh-tool/types.ts +97 -0
- package/src/gh-tool/workflow.ts +542 -0
- package/src/index.ts +1 -0
- package/src/k8s-tool/errors.ts +21 -0
- package/src/k8s-tool/index.ts +151 -0
- package/src/k8s-tool/service.ts +227 -0
- package/src/k8s-tool/types.ts +9 -0
- package/src/logs-tool/errors.ts +29 -0
- package/src/logs-tool/index.ts +176 -0
- package/src/logs-tool/service.ts +323 -0
- package/src/logs-tool/types.ts +40 -0
- package/src/session-tool/config.ts +55 -0
- package/src/session-tool/errors.ts +38 -0
- package/src/session-tool/index.ts +270 -0
- package/src/session-tool/service.ts +210 -0
- package/src/session-tool/types.ts +28 -0
- package/src/shared/bun.ts +59 -0
- package/src/shared/cli.ts +38 -0
- package/src/shared/error-renderer.ts +42 -0
- package/src/shared/exec.ts +62 -0
- package/src/shared/format.ts +27 -0
- package/src/shared/index.ts +16 -0
- package/src/shared/throttle.ts +35 -0
- package/src/shared/types.ts +25 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { Console, Effect, Option } from "effect";
|
|
2
|
+
|
|
3
|
+
import type { BranchPRDetail, CheckResult, MergeResult, MergeStrategy, PRInfo } from "../types";
|
|
4
|
+
|
|
5
|
+
import { GitHubCommandError, GitHubMergeError, GitHubTimeoutError } from "../errors";
|
|
6
|
+
import { GitHubService } from "../service";
|
|
7
|
+
|
|
8
|
+
import type { ButStatusJson, PRViewJsonResult } from "./helpers";
|
|
9
|
+
import { runLocalCommand } from "./helpers";
|
|
10
|
+
|
|
11
|
+
export const viewPR = Effect.fn("pr.viewPR")(function* (prNumber: number | null) {
|
|
12
|
+
const gh = yield* GitHubService;
|
|
13
|
+
|
|
14
|
+
const args = ["pr", "view"];
|
|
15
|
+
if (prNumber !== null) {
|
|
16
|
+
args.push(String(prNumber));
|
|
17
|
+
}
|
|
18
|
+
args.push("--json", "number,url,title,headRefName,baseRefName,state,isDraft");
|
|
19
|
+
|
|
20
|
+
const info = yield* gh.runGhJson<PRInfo>(args);
|
|
21
|
+
return info;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const detectPRStatus = Effect.fn("pr.detectPRStatus")(function* () {
|
|
25
|
+
const directPr = yield* viewPR(null).pipe(Effect.option);
|
|
26
|
+
if (Option.isSome(directPr)) {
|
|
27
|
+
return {
|
|
28
|
+
mode: "single" as const,
|
|
29
|
+
pr: directPr.value,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const currentBranchResult = yield* runLocalCommand("git", [
|
|
34
|
+
"symbolic-ref",
|
|
35
|
+
"--short",
|
|
36
|
+
"HEAD",
|
|
37
|
+
]).pipe(Effect.option);
|
|
38
|
+
|
|
39
|
+
if (Option.isNone(currentBranchResult)) {
|
|
40
|
+
return {
|
|
41
|
+
mode: "none" as const,
|
|
42
|
+
branches: [] as BranchPRDetail[],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const currentBranch = currentBranchResult.value.stdout;
|
|
47
|
+
const isGitButlerWorkspace = currentBranch === "gitbutler/workspace";
|
|
48
|
+
|
|
49
|
+
if (!isGitButlerWorkspace) {
|
|
50
|
+
return {
|
|
51
|
+
mode: "none" as const,
|
|
52
|
+
branches: [] as BranchPRDetail[],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const butStatusResult = yield* runLocalCommand("but", ["status", "--json"]);
|
|
57
|
+
|
|
58
|
+
const butStatus = yield* Effect.try({
|
|
59
|
+
try: () => JSON.parse(butStatusResult.stdout) as ButStatusJson,
|
|
60
|
+
catch: (error) =>
|
|
61
|
+
new GitHubCommandError({
|
|
62
|
+
command: "but status --json",
|
|
63
|
+
exitCode: 0,
|
|
64
|
+
stderr: `Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
65
|
+
message: `Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
66
|
+
}),
|
|
67
|
+
}).pipe(Effect.mapError((error) => error as GitHubCommandError));
|
|
68
|
+
|
|
69
|
+
const branchNames = [
|
|
70
|
+
...new Set(
|
|
71
|
+
butStatus.stacks.flatMap((stack) =>
|
|
72
|
+
stack.branches.map((branch) => branch.name).filter((name) => name.length > 0),
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const gh = yield* GitHubService;
|
|
78
|
+
|
|
79
|
+
type BranchResult = {
|
|
80
|
+
branch: string;
|
|
81
|
+
openPr: PRInfo | null;
|
|
82
|
+
closedPr: {
|
|
83
|
+
number: number;
|
|
84
|
+
url: string;
|
|
85
|
+
state: "MERGED" | "CLOSED";
|
|
86
|
+
} | null;
|
|
87
|
+
remoteExists: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const branchResults = yield* Effect.all(
|
|
91
|
+
branchNames.map((branchName) =>
|
|
92
|
+
Effect.all(
|
|
93
|
+
{
|
|
94
|
+
openPr: gh
|
|
95
|
+
.runGhJson<PRInfo[]>([
|
|
96
|
+
"pr",
|
|
97
|
+
"list",
|
|
98
|
+
"--head",
|
|
99
|
+
branchName,
|
|
100
|
+
"--json",
|
|
101
|
+
"number,url,title,headRefName,baseRefName,state,isDraft",
|
|
102
|
+
"--limit",
|
|
103
|
+
"1",
|
|
104
|
+
])
|
|
105
|
+
.pipe(
|
|
106
|
+
Effect.map((prs) => prs[0] ?? null),
|
|
107
|
+
Effect.catchTag("GitHubCommandError", () => Effect.succeed(null)),
|
|
108
|
+
),
|
|
109
|
+
closedPr: gh
|
|
110
|
+
.runGhJson<
|
|
111
|
+
Array<{
|
|
112
|
+
number: number;
|
|
113
|
+
url: string;
|
|
114
|
+
state: string;
|
|
115
|
+
}>
|
|
116
|
+
>([
|
|
117
|
+
"pr",
|
|
118
|
+
"list",
|
|
119
|
+
"--head",
|
|
120
|
+
branchName,
|
|
121
|
+
"--state",
|
|
122
|
+
"closed",
|
|
123
|
+
"--json",
|
|
124
|
+
"number,url,state",
|
|
125
|
+
"--limit",
|
|
126
|
+
"1",
|
|
127
|
+
])
|
|
128
|
+
.pipe(
|
|
129
|
+
Effect.map((prs) => {
|
|
130
|
+
const pr = prs[0];
|
|
131
|
+
if (!pr) return null;
|
|
132
|
+
return {
|
|
133
|
+
number: pr.number,
|
|
134
|
+
url: pr.url,
|
|
135
|
+
state: pr.state as "MERGED" | "CLOSED",
|
|
136
|
+
};
|
|
137
|
+
}),
|
|
138
|
+
Effect.catchTag("GitHubCommandError", () => Effect.succeed(null)),
|
|
139
|
+
),
|
|
140
|
+
remoteExists: runLocalCommand("git", ["ls-remote", "--heads", "origin", branchName]).pipe(
|
|
141
|
+
Effect.map((result) => result.stdout.trim().length > 0),
|
|
142
|
+
Effect.catch(() => Effect.succeed(false)),
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
{ concurrency: "unbounded" },
|
|
146
|
+
).pipe(
|
|
147
|
+
Effect.map(
|
|
148
|
+
(r): BranchResult => ({
|
|
149
|
+
branch: branchName,
|
|
150
|
+
...r,
|
|
151
|
+
}),
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
{ concurrency: "unbounded" },
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const foundPrs = branchResults.filter((r) => r.openPr !== null).map((r) => r.openPr!);
|
|
159
|
+
|
|
160
|
+
if (foundPrs.length === 0) {
|
|
161
|
+
const branchDetails: BranchPRDetail[] = branchResults.map((r) => ({
|
|
162
|
+
branch: r.branch,
|
|
163
|
+
remoteExists: r.remoteExists,
|
|
164
|
+
closedPr: r.closedPr,
|
|
165
|
+
}));
|
|
166
|
+
return {
|
|
167
|
+
mode: "none" as const,
|
|
168
|
+
branches: branchDetails,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (foundPrs.length === 1) {
|
|
173
|
+
return {
|
|
174
|
+
mode: "single" as const,
|
|
175
|
+
pr: foundPrs[0]!,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
mode: "multiple" as const,
|
|
181
|
+
prs: foundPrs,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export const createPR = Effect.fn("pr.createPR")(function* (opts: {
|
|
186
|
+
base: string;
|
|
187
|
+
title: string;
|
|
188
|
+
body: string;
|
|
189
|
+
draft: boolean;
|
|
190
|
+
head: string | null;
|
|
191
|
+
}) {
|
|
192
|
+
const gh = yield* GitHubService;
|
|
193
|
+
|
|
194
|
+
// When --head is provided (e.g. GitButler workspace), use `gh pr list --head`
|
|
195
|
+
// to find existing PR since `gh pr view` relies on the current git branch.
|
|
196
|
+
const existing = yield* opts.head !== null
|
|
197
|
+
? gh
|
|
198
|
+
.runGhJson<PRInfo[]>([
|
|
199
|
+
"pr",
|
|
200
|
+
"list",
|
|
201
|
+
"--head",
|
|
202
|
+
opts.head,
|
|
203
|
+
"--json",
|
|
204
|
+
"number,url,title,headRefName,baseRefName,state,isDraft",
|
|
205
|
+
"--limit",
|
|
206
|
+
"1",
|
|
207
|
+
])
|
|
208
|
+
.pipe(Effect.map((prs) => (prs.length > 0 ? Option.some(prs[0]!) : Option.none())))
|
|
209
|
+
: gh
|
|
210
|
+
.runGhJson<{ number: number; url: string }>(["pr", "view", "--json", "number,url"])
|
|
211
|
+
.pipe(Effect.option);
|
|
212
|
+
|
|
213
|
+
if (Option.isSome(existing)) {
|
|
214
|
+
const pr = existing.value;
|
|
215
|
+
yield* gh.runGh(["pr", "edit", String(pr.number), "--title", opts.title, "--body", opts.body]);
|
|
216
|
+
|
|
217
|
+
return yield* viewPR(pr.number);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const createArgs = [
|
|
221
|
+
"pr",
|
|
222
|
+
"create",
|
|
223
|
+
"--base",
|
|
224
|
+
opts.base,
|
|
225
|
+
"--title",
|
|
226
|
+
opts.title,
|
|
227
|
+
"--body",
|
|
228
|
+
opts.body,
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
if (opts.head !== null) {
|
|
232
|
+
createArgs.push("--head", opts.head);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (opts.draft) {
|
|
236
|
+
createArgs.push("--draft");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const createResult = yield* gh.runGh(createArgs);
|
|
240
|
+
|
|
241
|
+
if (opts.head === null) {
|
|
242
|
+
return yield* viewPR(null);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const urlMatch = createResult.stdout.match(/\/pull\/(\d+)/);
|
|
246
|
+
if (urlMatch?.[1]) {
|
|
247
|
+
return yield* viewPR(Number(urlMatch[1]));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const prs = yield* gh.runGhJson<PRInfo[]>([
|
|
251
|
+
"pr",
|
|
252
|
+
"list",
|
|
253
|
+
"--head",
|
|
254
|
+
opts.head,
|
|
255
|
+
"--json",
|
|
256
|
+
"number,url,title,headRefName,baseRefName,state,isDraft",
|
|
257
|
+
"--limit",
|
|
258
|
+
"1",
|
|
259
|
+
]);
|
|
260
|
+
if (prs.length > 0) {
|
|
261
|
+
return prs[0]!;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return yield* Effect.fail(
|
|
265
|
+
new GitHubCommandError({
|
|
266
|
+
command: `gh pr create --head ${opts.head}`,
|
|
267
|
+
exitCode: 0,
|
|
268
|
+
stderr: "Pull request was created but could not be resolved by head branch.",
|
|
269
|
+
message: "Pull request was created but could not be resolved by head branch.",
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
export const mergePR = Effect.fn("pr.mergePR")(function* (opts: {
|
|
275
|
+
pr: number;
|
|
276
|
+
strategy: MergeStrategy;
|
|
277
|
+
deleteBranch: boolean;
|
|
278
|
+
confirm: boolean;
|
|
279
|
+
}) {
|
|
280
|
+
const gh = yield* GitHubService;
|
|
281
|
+
|
|
282
|
+
const info = yield* gh.runGhJson<PRViewJsonResult>([
|
|
283
|
+
"pr",
|
|
284
|
+
"view",
|
|
285
|
+
String(opts.pr),
|
|
286
|
+
"--json",
|
|
287
|
+
"number,url,title,headRefName,baseRefName,state,isDraft,mergeable",
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
if (!opts.confirm) {
|
|
291
|
+
const mergeableNote =
|
|
292
|
+
info.mergeable === "MERGEABLE"
|
|
293
|
+
? "PR is mergeable."
|
|
294
|
+
: `PR mergeable status: ${info.mergeable}`;
|
|
295
|
+
|
|
296
|
+
yield* Console.log(
|
|
297
|
+
`DRY RUN: Would merge PR #${info.number} "${info.title}" via ${opts.strategy.toUpperCase()}. ` +
|
|
298
|
+
`Branch \`${info.headRefName}\` → \`${info.baseRefName}\`. ` +
|
|
299
|
+
(opts.deleteBranch ? `Branch \`${info.headRefName}\` will be deleted. ` : "") +
|
|
300
|
+
mergeableNote,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const result: MergeResult = {
|
|
304
|
+
merged: false,
|
|
305
|
+
strategy: opts.strategy,
|
|
306
|
+
branchDeleted: false,
|
|
307
|
+
sha: null,
|
|
308
|
+
};
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const mergeArgs = ["pr", "merge", String(opts.pr), `--${opts.strategy}`];
|
|
313
|
+
|
|
314
|
+
if (opts.deleteBranch) {
|
|
315
|
+
mergeArgs.push("--delete-branch");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const mergeResult = yield* gh.runGh(mergeArgs).pipe(
|
|
319
|
+
Effect.catchTag("GitHubCommandError", (error) => {
|
|
320
|
+
const stderr = error.stderr.toLowerCase();
|
|
321
|
+
|
|
322
|
+
if (stderr.includes("merge conflict") || stderr.includes("conflicts")) {
|
|
323
|
+
return Effect.fail(
|
|
324
|
+
new GitHubMergeError({
|
|
325
|
+
message: `PR #${opts.pr} has merge conflicts`,
|
|
326
|
+
reason: "conflicts",
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (stderr.includes("required status check") || stderr.includes("checks")) {
|
|
332
|
+
return Effect.fail(
|
|
333
|
+
new GitHubMergeError({
|
|
334
|
+
message: `PR #${opts.pr} has failing required checks`,
|
|
335
|
+
reason: "checks_failing",
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (stderr.includes("protected branch")) {
|
|
341
|
+
return Effect.fail(
|
|
342
|
+
new GitHubMergeError({
|
|
343
|
+
message: `PR #${opts.pr} targets a protected branch`,
|
|
344
|
+
reason: "branch_protected",
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return Effect.fail(
|
|
350
|
+
new GitHubMergeError({
|
|
351
|
+
message: `Failed to merge PR #${opts.pr}: ${error.stderr}`,
|
|
352
|
+
reason: "unknown",
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const shaMatch = mergeResult.stdout.match(/([0-9a-f]{7,40})/);
|
|
359
|
+
|
|
360
|
+
const result: MergeResult = {
|
|
361
|
+
merged: true,
|
|
362
|
+
strategy: opts.strategy,
|
|
363
|
+
branchDeleted: opts.deleteBranch,
|
|
364
|
+
sha: shaMatch?.[1] ?? null,
|
|
365
|
+
};
|
|
366
|
+
return result;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
export const editPR = Effect.fn("pr.editPR")(function* (opts: {
|
|
370
|
+
pr: number;
|
|
371
|
+
title: string | null;
|
|
372
|
+
body: string | null;
|
|
373
|
+
}) {
|
|
374
|
+
if (!opts.title && !opts.body) {
|
|
375
|
+
return yield* Effect.fail(
|
|
376
|
+
new GitHubCommandError({
|
|
377
|
+
command: "pr edit",
|
|
378
|
+
exitCode: 1,
|
|
379
|
+
stderr: "At least one of --title or --body must be provided",
|
|
380
|
+
message: "At least one of --title or --body must be provided",
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const gh = yield* GitHubService;
|
|
386
|
+
|
|
387
|
+
const editArgs = ["pr", "edit", String(opts.pr)];
|
|
388
|
+
|
|
389
|
+
if (opts.title) {
|
|
390
|
+
editArgs.push("--title", opts.title);
|
|
391
|
+
}
|
|
392
|
+
if (opts.body) {
|
|
393
|
+
editArgs.push("--body", opts.body);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
yield* gh.runGh(editArgs);
|
|
397
|
+
|
|
398
|
+
return yield* viewPR(opts.pr);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
export const fetchChecks = Effect.fn("pr.fetchChecks")(function* (
|
|
402
|
+
pr: number | null,
|
|
403
|
+
watch: boolean,
|
|
404
|
+
failFast: boolean,
|
|
405
|
+
timeoutSeconds: number,
|
|
406
|
+
) {
|
|
407
|
+
const gh = yield* GitHubService;
|
|
408
|
+
|
|
409
|
+
const args = ["pr", "checks"];
|
|
410
|
+
if (pr !== null) {
|
|
411
|
+
args.push(String(pr));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (watch) {
|
|
415
|
+
const watchArgs = [...args, "--watch"];
|
|
416
|
+
if (failFast) {
|
|
417
|
+
watchArgs.push("--fail-fast");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
421
|
+
yield* gh.runGh(watchArgs).pipe(
|
|
422
|
+
Effect.timeoutOrElse({
|
|
423
|
+
duration: timeoutMs,
|
|
424
|
+
onTimeout: () =>
|
|
425
|
+
Effect.fail(
|
|
426
|
+
new GitHubTimeoutError({
|
|
427
|
+
message: `CI check monitoring timed out after ${timeoutSeconds}s`,
|
|
428
|
+
timeoutMs,
|
|
429
|
+
}),
|
|
430
|
+
),
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
return yield* gh.runGhJson<CheckResult[]>([...args, "--json", "name,state,bucket,link"]);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return yield* gh.runGhJson<CheckResult[]>([...args, "--json", "name,state,bucket,link"]);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
export const fetchFailedChecks = Effect.fn("pr.fetchFailedChecks")(function* (pr: number | null) {
|
|
441
|
+
const checks = yield* fetchChecks(pr, false, false, 0);
|
|
442
|
+
return checks.filter((check) => check.bucket === "fail");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
export const rerunChecks = Effect.fn("pr.rerunChecks")(function* (
|
|
446
|
+
pr: number | null,
|
|
447
|
+
failedOnly: boolean,
|
|
448
|
+
) {
|
|
449
|
+
const gh = yield* GitHubService;
|
|
450
|
+
|
|
451
|
+
const checks = yield* gh.runGhJson<
|
|
452
|
+
Array<{
|
|
453
|
+
name: string;
|
|
454
|
+
link: string;
|
|
455
|
+
bucket: string;
|
|
456
|
+
state: string;
|
|
457
|
+
}>
|
|
458
|
+
>(["pr", "checks", ...(pr !== null ? [String(pr)] : []), "--json", "name,link,bucket,state"]);
|
|
459
|
+
|
|
460
|
+
// Extract unique GitHub Actions run IDs from links
|
|
461
|
+
const runIds = new Set<string>();
|
|
462
|
+
for (const check of failedOnly ? checks.filter((c) => c.bucket === "fail") : checks) {
|
|
463
|
+
const match = check.link.match(/github\.com\/[^/]+\/[^/]+\/actions\/runs\/(\d+)/);
|
|
464
|
+
if (match?.[1]) {
|
|
465
|
+
runIds.add(match[1]);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (runIds.size === 0) {
|
|
470
|
+
return {
|
|
471
|
+
rerun: 0,
|
|
472
|
+
message: failedOnly
|
|
473
|
+
? "No failed GitHub Actions runs found to rerun"
|
|
474
|
+
: "No GitHub Actions runs found to rerun",
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const results: Array<{
|
|
479
|
+
runId: string;
|
|
480
|
+
success: boolean;
|
|
481
|
+
}> = [];
|
|
482
|
+
for (const runId of runIds) {
|
|
483
|
+
const rerunArgs = failedOnly ? ["run", "rerun", runId, "--failed"] : ["run", "rerun", runId];
|
|
484
|
+
const success = yield* gh.runGh(rerunArgs).pipe(
|
|
485
|
+
Effect.map(() => true),
|
|
486
|
+
Effect.catch(() => Effect.succeed(false)),
|
|
487
|
+
);
|
|
488
|
+
results.push({ runId, success });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
rerun: results.filter((r) => r.success).length,
|
|
493
|
+
failed: results.filter((r) => !r.success).length,
|
|
494
|
+
runs: results,
|
|
495
|
+
message: `Rerun ${results.filter((r) => r.success).length}/${results.length} GitHub Actions runs`,
|
|
496
|
+
};
|
|
497
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
|
+
import { Effect, Stream } from "effect";
|
|
3
|
+
|
|
4
|
+
import { GitHubCommandError } from "../errors";
|
|
5
|
+
|
|
6
|
+
export type LocalCommandResult = {
|
|
7
|
+
stdout: string;
|
|
8
|
+
stderr: string;
|
|
9
|
+
exitCode: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ButStatusJson = {
|
|
13
|
+
stacks: Array<{
|
|
14
|
+
branches: Array<{ name: string }>;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type PRViewJsonResult = {
|
|
19
|
+
number: number;
|
|
20
|
+
url: string;
|
|
21
|
+
title: string;
|
|
22
|
+
headRefName: string;
|
|
23
|
+
baseRefName: string;
|
|
24
|
+
state: string;
|
|
25
|
+
isDraft: boolean;
|
|
26
|
+
mergeable: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const runLocalCommand = Effect.fn("pr.runLocalCommand")(function* (
|
|
30
|
+
binary: string,
|
|
31
|
+
args: string[],
|
|
32
|
+
) {
|
|
33
|
+
const executor = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
34
|
+
|
|
35
|
+
const command = ChildProcess.make(binary, args, {
|
|
36
|
+
stdout: "pipe",
|
|
37
|
+
stderr: "pipe",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const result = yield* Effect.scoped(
|
|
41
|
+
Effect.gen(function* () {
|
|
42
|
+
const proc = yield* executor.spawn(command);
|
|
43
|
+
|
|
44
|
+
const stdoutChunk = yield* proc.stdout.pipe(Stream.decodeText(), Stream.runCollect);
|
|
45
|
+
const stdout = stdoutChunk.join("");
|
|
46
|
+
|
|
47
|
+
const stderrChunk = yield* proc.stderr.pipe(Stream.decodeText(), Stream.runCollect);
|
|
48
|
+
const stderr = stderrChunk.join("");
|
|
49
|
+
|
|
50
|
+
const exitCode = yield* proc.exitCode;
|
|
51
|
+
|
|
52
|
+
const commandText = [binary, ...args].join(" ");
|
|
53
|
+
if (exitCode !== 0) {
|
|
54
|
+
return yield* Effect.fail(
|
|
55
|
+
new GitHubCommandError({
|
|
56
|
+
message: stderr.trim(),
|
|
57
|
+
command: commandText,
|
|
58
|
+
exitCode: exitCode as number,
|
|
59
|
+
stderr: stderr.trim(),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const commandResult: LocalCommandResult = {
|
|
65
|
+
stdout: stdout.trim(),
|
|
66
|
+
stderr: stderr.trim(),
|
|
67
|
+
exitCode: exitCode as number,
|
|
68
|
+
};
|
|
69
|
+
return commandResult;
|
|
70
|
+
}),
|
|
71
|
+
).pipe(
|
|
72
|
+
Effect.mapError(
|
|
73
|
+
(error) =>
|
|
74
|
+
new GitHubCommandError({
|
|
75
|
+
command: [binary, ...args].join(" "),
|
|
76
|
+
exitCode: -1,
|
|
77
|
+
stderr: String(error),
|
|
78
|
+
message: String(error),
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
prChecksCommand,
|
|
3
|
+
prChecksFailedCommand,
|
|
4
|
+
prCommentCommand,
|
|
5
|
+
prCommentsCommand,
|
|
6
|
+
prCreateCommand,
|
|
7
|
+
prDiscussionSummaryCommand,
|
|
8
|
+
prEditCommand,
|
|
9
|
+
prIssueCommentsCommand,
|
|
10
|
+
prIssueCommentsLatestCommand,
|
|
11
|
+
prMergeCommand,
|
|
12
|
+
prReplyCommand,
|
|
13
|
+
prRerunChecksCommand,
|
|
14
|
+
prResolveCommand,
|
|
15
|
+
prStatusCommand,
|
|
16
|
+
prSubmitReviewCommand,
|
|
17
|
+
prThreadsCommand,
|
|
18
|
+
prViewCommand,
|
|
19
|
+
} from "./commands";
|