@timmo001/effect-gh 0.1.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/LICENSE +201 -0
- package/README.md +299 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +87 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +14 -0
- package/dist/gh.d.ts +38 -0
- package/dist/gh.js +96 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/issue.d.ts +58 -0
- package/dist/issue.js +49 -0
- package/dist/pull-request.d.ts +78 -0
- package/dist/pull-request.js +79 -0
- package/dist/repository.d.ts +21 -0
- package/dist/repository.js +21 -0
- package/dist/workflow.d.ts +89 -0
- package/dist/workflow.js +138 -0
- package/package.json +62 -0
- package/src/api.ts +165 -0
- package/src/errors.ts +29 -0
- package/src/gh.ts +236 -0
- package/src/index.ts +26 -0
- package/src/issue.ts +94 -0
- package/src/pull-request.ts +144 -0
- package/src/repository.ts +30 -0
- package/src/workflow.ts +198 -0
package/src/issue.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Effect, Predicate, Schema } from "effect";
|
|
2
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
3
|
+
|
|
4
|
+
export const IssueSummary = Schema.Struct({
|
|
5
|
+
number: Schema.Int,
|
|
6
|
+
title: Schema.String,
|
|
7
|
+
url: Schema.String,
|
|
8
|
+
state: Schema.Literals(["OPEN", "CLOSED"]),
|
|
9
|
+
updatedAt: Schema.String,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export interface IssueSummary extends Schema.Schema.Type<typeof IssueSummary> {}
|
|
13
|
+
|
|
14
|
+
export const Issue = Schema.Struct({
|
|
15
|
+
...IssueSummary.fields,
|
|
16
|
+
body: Schema.String,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export interface Issue extends Schema.Schema.Type<typeof Issue> {}
|
|
20
|
+
|
|
21
|
+
export interface ViewOptions {
|
|
22
|
+
/** Select [HOST/]OWNER/REPO instead of the working directory's repository. */
|
|
23
|
+
readonly repo?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ListOptions extends ViewOptions {
|
|
27
|
+
/** Defaults to open. */
|
|
28
|
+
readonly state?: "open" | "closed" | "all";
|
|
29
|
+
/** Maximum issues to fetch, defaults to gh's 30; not an unbounded list. */
|
|
30
|
+
readonly limit?: number;
|
|
31
|
+
/** Repeated gh label filters; comma-separated values follow gh's label syntax. */
|
|
32
|
+
readonly labels?: ReadonlyArray<string>;
|
|
33
|
+
readonly assignee?: string;
|
|
34
|
+
readonly search?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class InvalidInput extends Schema.TaggedError<InvalidInput>()(
|
|
38
|
+
"IssueInvalidInput",
|
|
39
|
+
{ cause: Schema.Defect() },
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
const positiveInteger = Schema.Int.check(Schema.isGreaterThan(0));
|
|
43
|
+
|
|
44
|
+
const summaryFields = "number,title,url,state,updatedAt";
|
|
45
|
+
|
|
46
|
+
/** Fetch a bounded list using gh's default state and limit unless overridden. */
|
|
47
|
+
export const list = Effect.fn("Issue.list")(function* (
|
|
48
|
+
options: ListOptions = {},
|
|
49
|
+
execution?: GhOptions,
|
|
50
|
+
) {
|
|
51
|
+
const args = ["issue", "list", "--json", summaryFields];
|
|
52
|
+
|
|
53
|
+
if (options.repo !== undefined) args.push("--repo", options.repo);
|
|
54
|
+
|
|
55
|
+
if (options.state !== undefined) args.push("--state", options.state);
|
|
56
|
+
|
|
57
|
+
if (options.limit !== undefined) {
|
|
58
|
+
const limit = yield* Schema.decodeEffect(positiveInteger)(
|
|
59
|
+
options.limit,
|
|
60
|
+
).pipe(Effect.mapError((cause) => new InvalidInput({ cause })));
|
|
61
|
+
|
|
62
|
+
args.push("--limit", String(limit));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const label of options.labels ?? []) args.push("--label", label);
|
|
66
|
+
|
|
67
|
+
if (options.assignee !== undefined) args.push("--assignee", options.assignee);
|
|
68
|
+
|
|
69
|
+
if (options.search !== undefined) args.push("--search", options.search);
|
|
70
|
+
const gh = yield* Gh;
|
|
71
|
+
|
|
72
|
+
return yield* gh.json(args, Schema.Array(IssueSummary), execution);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** View an issue by number or URL, including its body. */
|
|
76
|
+
export const view = Effect.fn("Issue.view")(function* (
|
|
77
|
+
issue: number | string,
|
|
78
|
+
options: ViewOptions = {},
|
|
79
|
+
execution?: GhOptions,
|
|
80
|
+
) {
|
|
81
|
+
if (Predicate.isNumber(issue)) {
|
|
82
|
+
yield* Schema.decodeEffect(positiveInteger)(issue).pipe(
|
|
83
|
+
Effect.mapError((cause) => new InvalidInput({ cause })),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const gh = yield* Gh;
|
|
88
|
+
const args = ["issue", "view", "--json", `${summaryFields},body`];
|
|
89
|
+
|
|
90
|
+
if (options.repo !== undefined) args.push("--repo", options.repo);
|
|
91
|
+
args.push("--", String(issue));
|
|
92
|
+
|
|
93
|
+
return yield* gh.json(args, Issue, execution);
|
|
94
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Effect, Match, Predicate, Schema, Stream } from "effect";
|
|
2
|
+
import { GhDecodeError } from "./errors.js";
|
|
3
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
4
|
+
|
|
5
|
+
export const Summary = Schema.Struct({
|
|
6
|
+
number: Schema.Int,
|
|
7
|
+
title: Schema.String,
|
|
8
|
+
url: Schema.String,
|
|
9
|
+
state: Schema.Literals(["OPEN", "CLOSED", "MERGED"]),
|
|
10
|
+
isDraft: Schema.Boolean,
|
|
11
|
+
headRefName: Schema.String,
|
|
12
|
+
headRefOid: Schema.String,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export interface Summary extends Schema.Schema.Type<typeof Summary> {}
|
|
16
|
+
|
|
17
|
+
export const Check = Schema.Struct({
|
|
18
|
+
name: Schema.String,
|
|
19
|
+
state: Schema.String,
|
|
20
|
+
bucket: Schema.Literals(["pass", "fail", "pending", "skipping", "cancel"]),
|
|
21
|
+
link: Schema.NullOr(Schema.String),
|
|
22
|
+
workflow: Schema.NullOr(Schema.String),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export interface Check extends Schema.Schema.Type<typeof Check> {}
|
|
26
|
+
|
|
27
|
+
export const ChecksResult = Schema.Struct({
|
|
28
|
+
checks: Schema.Array(Check),
|
|
29
|
+
status: Schema.Literals(["success", "failure", "pending"]),
|
|
30
|
+
exitCode: Schema.Literals([0, 1, 8]),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export interface ChecksResult extends Schema.Schema.Type<typeof ChecksResult> {}
|
|
34
|
+
|
|
35
|
+
export interface Options extends GhOptions {
|
|
36
|
+
readonly repository: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ListOptions extends Options {
|
|
40
|
+
readonly state?: "open" | "closed" | "merged" | "all";
|
|
41
|
+
readonly limit?: number;
|
|
42
|
+
readonly base?: string;
|
|
43
|
+
readonly head?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ChecksOptions extends Options {
|
|
47
|
+
readonly required?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fields = "number,title,url,state,isDraft,headRefName,headRefOid";
|
|
51
|
+
|
|
52
|
+
export const list = Effect.fn("PullRequest.list")(function* (
|
|
53
|
+
options: ListOptions,
|
|
54
|
+
) {
|
|
55
|
+
const gh = yield* Gh;
|
|
56
|
+
|
|
57
|
+
const args = [
|
|
58
|
+
"pr",
|
|
59
|
+
"list",
|
|
60
|
+
`--repo=${options.repository}`,
|
|
61
|
+
`--state=${options.state ?? "open"}`,
|
|
62
|
+
`--limit=${options.limit ?? 30}`,
|
|
63
|
+
`--json=${fields}`,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
if (options.base !== undefined) args.push(`--base=${options.base}`);
|
|
67
|
+
|
|
68
|
+
if (options.head !== undefined) args.push(`--head=${options.head}`);
|
|
69
|
+
|
|
70
|
+
return yield* gh.json(args, Schema.Array(Summary), options);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const view = Effect.fn("PullRequest.view")(function* (
|
|
74
|
+
selector: number | string,
|
|
75
|
+
options: Options,
|
|
76
|
+
) {
|
|
77
|
+
const gh = yield* Gh;
|
|
78
|
+
|
|
79
|
+
return yield* gh.json(
|
|
80
|
+
[
|
|
81
|
+
"pr",
|
|
82
|
+
"view",
|
|
83
|
+
`--repo=${options.repository}`,
|
|
84
|
+
`--json=${fields}`,
|
|
85
|
+
"--",
|
|
86
|
+
String(selector),
|
|
87
|
+
],
|
|
88
|
+
Summary,
|
|
89
|
+
options,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/** Failed and pending checks are data; invalid output retains the command error. */
|
|
94
|
+
export const checks = Effect.fn("PullRequest.checks")(function* (
|
|
95
|
+
selector: number | string,
|
|
96
|
+
options: ChecksOptions,
|
|
97
|
+
) {
|
|
98
|
+
const gh = yield* Gh;
|
|
99
|
+
|
|
100
|
+
const args = [
|
|
101
|
+
"pr",
|
|
102
|
+
"checks",
|
|
103
|
+
`--repo=${options.repository}`,
|
|
104
|
+
"--json=name,state,bucket,link,workflow",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
if (options.required) args.push("--required");
|
|
108
|
+
args.push("--", String(selector));
|
|
109
|
+
|
|
110
|
+
let stdout = "";
|
|
111
|
+
|
|
112
|
+
const commandError = yield* gh.stream(args, options).pipe(
|
|
113
|
+
Stream.runForEach((chunk) =>
|
|
114
|
+
Effect.sync(() => {
|
|
115
|
+
if (Predicate.isTagged(chunk, "Stdout")) stdout += chunk.text;
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
Effect.as(undefined),
|
|
119
|
+
Effect.catchTag("GhCommandError", (error) =>
|
|
120
|
+
error.exitCode === 1 || error.exitCode === 8
|
|
121
|
+
? Effect.succeed(error)
|
|
122
|
+
: Effect.fail(error),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const decoded = yield* Schema.decodeEffect(
|
|
127
|
+
Schema.fromJsonString(Schema.Array(Check)),
|
|
128
|
+
)(stdout).pipe(
|
|
129
|
+
Effect.mapError((cause) => commandError ?? new GhDecodeError({ cause })),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const exitCode = commandError?.exitCode === 8 ? 8 : commandError ? 1 : 0;
|
|
133
|
+
|
|
134
|
+
return ChecksResult.make({
|
|
135
|
+
checks: decoded,
|
|
136
|
+
status: Match.value(exitCode).pipe(
|
|
137
|
+
Match.when(8, () => "pending" as const),
|
|
138
|
+
Match.when(1, () => "failure" as const),
|
|
139
|
+
Match.when(0, () => "success" as const),
|
|
140
|
+
Match.exhaustive,
|
|
141
|
+
),
|
|
142
|
+
exitCode,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
3
|
+
|
|
4
|
+
export const Repository = Schema.Struct({
|
|
5
|
+
nameWithOwner: Schema.String,
|
|
6
|
+
url: Schema.String,
|
|
7
|
+
defaultBranchRef: Schema.NullOr(Schema.Struct({ name: Schema.String })),
|
|
8
|
+
isPrivate: Schema.Boolean,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export interface Repository extends Schema.Schema.Type<typeof Repository> {}
|
|
12
|
+
|
|
13
|
+
/** View [HOST/]OWNER/REPO, or the repository selected by the working directory. */
|
|
14
|
+
export const view = Effect.fn("Repository.view")(function* (
|
|
15
|
+
repo?: string,
|
|
16
|
+
options?: GhOptions,
|
|
17
|
+
) {
|
|
18
|
+
const gh = yield* Gh;
|
|
19
|
+
|
|
20
|
+
const args = [
|
|
21
|
+
"repo",
|
|
22
|
+
"view",
|
|
23
|
+
"--json",
|
|
24
|
+
"nameWithOwner,url,defaultBranchRef,isPrivate",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
if (repo !== undefined) args.push("--", repo);
|
|
28
|
+
|
|
29
|
+
return yield* gh.json(args, Repository, options);
|
|
30
|
+
});
|
package/src/workflow.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
3
|
+
|
|
4
|
+
const positiveInteger = Schema.Int.check(Schema.isGreaterThan(0));
|
|
5
|
+
|
|
6
|
+
const repository = Schema.String.check(
|
|
7
|
+
Schema.isPattern(
|
|
8
|
+
/^(?:[a-zA-Z0-9.-]+(?::[0-9]+)?\/)?[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/,
|
|
9
|
+
),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const Run = Schema.Struct({
|
|
13
|
+
databaseId: positiveInteger,
|
|
14
|
+
attempt: positiveInteger,
|
|
15
|
+
headBranch: Schema.NullOr(Schema.String),
|
|
16
|
+
headSha: Schema.String,
|
|
17
|
+
status: Schema.String,
|
|
18
|
+
conclusion: Schema.NullOr(Schema.String),
|
|
19
|
+
url: Schema.String,
|
|
20
|
+
workflowName: Schema.NullOr(Schema.String),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export interface Run extends Schema.Schema.Type<typeof Run> {}
|
|
24
|
+
|
|
25
|
+
const fields = Object.keys(Run.fields).join(",");
|
|
26
|
+
|
|
27
|
+
const listOptions = Schema.Struct({
|
|
28
|
+
repo: repository,
|
|
29
|
+
limit: Schema.optionalKey(positiveInteger),
|
|
30
|
+
branch: Schema.optionalKey(Schema.NonEmptyString),
|
|
31
|
+
commit: Schema.optionalKey(Schema.NonEmptyString),
|
|
32
|
+
workflow: Schema.optionalKey(Schema.NonEmptyString),
|
|
33
|
+
status: Schema.optionalKey(
|
|
34
|
+
Schema.Literals([
|
|
35
|
+
"queued",
|
|
36
|
+
"completed",
|
|
37
|
+
"in_progress",
|
|
38
|
+
"requested",
|
|
39
|
+
"waiting",
|
|
40
|
+
"pending",
|
|
41
|
+
"action_required",
|
|
42
|
+
"cancelled",
|
|
43
|
+
"failure",
|
|
44
|
+
"neutral",
|
|
45
|
+
"skipped",
|
|
46
|
+
"stale",
|
|
47
|
+
"startup_failure",
|
|
48
|
+
"success",
|
|
49
|
+
"timed_out",
|
|
50
|
+
]),
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export interface ListOptions extends Schema.Schema.Type<typeof listOptions> {}
|
|
55
|
+
|
|
56
|
+
const viewOptions = Schema.Struct({
|
|
57
|
+
repo: repository,
|
|
58
|
+
runId: positiveInteger,
|
|
59
|
+
attempt: Schema.optionalKey(positiveInteger),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export interface ViewOptions extends Schema.Schema.Type<typeof viewOptions> {}
|
|
63
|
+
|
|
64
|
+
const logsOptions = Schema.Struct({
|
|
65
|
+
...viewOptions.fields,
|
|
66
|
+
failedOnly: Schema.optionalKey(Schema.Boolean),
|
|
67
|
+
job: Schema.optionalKey(positiveInteger),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export interface LogsOptions extends Schema.Schema.Type<typeof logsOptions> {}
|
|
71
|
+
|
|
72
|
+
const watchOptions = Schema.Struct({
|
|
73
|
+
repo: repository,
|
|
74
|
+
runId: positiveInteger,
|
|
75
|
+
interval: Schema.optionalKey(positiveInteger),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export interface WatchOptions extends Schema.Schema.Type<typeof watchOptions> {}
|
|
79
|
+
|
|
80
|
+
export class InvalidOptions extends Schema.TaggedError<InvalidOptions>()(
|
|
81
|
+
"WorkflowInvalidOptions",
|
|
82
|
+
{ cause: Schema.Defect() },
|
|
83
|
+
) {}
|
|
84
|
+
|
|
85
|
+
const decodeOptions = Effect.fn("Workflow.decodeOptions")(function* <
|
|
86
|
+
S extends Schema.Constraint,
|
|
87
|
+
>(schema: S, input: S["Type"]) {
|
|
88
|
+
return yield* Schema.decodeUnknownEffect(schema)(input).pipe(
|
|
89
|
+
Effect.mapError((cause) => new InvalidOptions({ cause })),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/** Fetch at most `limit` recent runs (default 20), without unbounded pagination. */
|
|
94
|
+
export const list = Effect.fn("Workflow.list")(function* (
|
|
95
|
+
input: ListOptions,
|
|
96
|
+
options?: GhOptions,
|
|
97
|
+
) {
|
|
98
|
+
const query = yield* decodeOptions(listOptions, input);
|
|
99
|
+
const gh = yield* Gh;
|
|
100
|
+
|
|
101
|
+
const args = [
|
|
102
|
+
"run",
|
|
103
|
+
"list",
|
|
104
|
+
"--repo",
|
|
105
|
+
query.repo,
|
|
106
|
+
"--limit",
|
|
107
|
+
String(query.limit ?? 20),
|
|
108
|
+
"--json",
|
|
109
|
+
fields,
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
if (query.branch !== undefined) args.push("--branch", query.branch);
|
|
113
|
+
|
|
114
|
+
if (query.commit !== undefined) args.push("--commit", query.commit);
|
|
115
|
+
|
|
116
|
+
if (query.workflow !== undefined) args.push("--workflow", query.workflow);
|
|
117
|
+
|
|
118
|
+
if (query.status !== undefined) args.push("--status", query.status);
|
|
119
|
+
|
|
120
|
+
return yield* gh.json(args, Schema.Array(Run), options);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** View the latest attempt unless an explicit attempt is supplied. */
|
|
124
|
+
export const view = Effect.fn("Workflow.view")(function* (
|
|
125
|
+
input: ViewOptions,
|
|
126
|
+
options?: GhOptions,
|
|
127
|
+
) {
|
|
128
|
+
const query = yield* decodeOptions(viewOptions, input);
|
|
129
|
+
const gh = yield* Gh;
|
|
130
|
+
|
|
131
|
+
const args = [
|
|
132
|
+
"run",
|
|
133
|
+
"view",
|
|
134
|
+
String(query.runId),
|
|
135
|
+
"--repo",
|
|
136
|
+
query.repo,
|
|
137
|
+
"--json",
|
|
138
|
+
fields,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
if (query.attempt !== undefined)
|
|
142
|
+
args.push("--attempt", String(query.attempt));
|
|
143
|
+
|
|
144
|
+
return yield* gh.json(args, Run, options);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/** Fetch full logs or failed steps, optionally for one attempt and job. */
|
|
148
|
+
export const logs = Effect.fn("Workflow.logs")(function* (
|
|
149
|
+
input: LogsOptions,
|
|
150
|
+
options?: GhOptions,
|
|
151
|
+
) {
|
|
152
|
+
const query = yield* decodeOptions(logsOptions, input);
|
|
153
|
+
const gh = yield* Gh;
|
|
154
|
+
|
|
155
|
+
const args = [
|
|
156
|
+
"run",
|
|
157
|
+
"view",
|
|
158
|
+
String(query.runId),
|
|
159
|
+
"--repo",
|
|
160
|
+
query.repo,
|
|
161
|
+
query.failedOnly ? "--log-failed" : "--log",
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
if (query.attempt !== undefined)
|
|
165
|
+
args.push("--attempt", String(query.attempt));
|
|
166
|
+
|
|
167
|
+
if (query.job !== undefined) args.push("--job", String(query.job));
|
|
168
|
+
|
|
169
|
+
return (yield* gh.execute(args, options)).stdout;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Stream native CLI progress, failing if the run fails. Requires gh with
|
|
174
|
+
* `--compact` support; gh watch cannot authenticate with fine-grained PATs.
|
|
175
|
+
* Inherits the core timeout; pass `{ timeout: null }` to disable it.
|
|
176
|
+
*/
|
|
177
|
+
export const watch = (input: WatchOptions, options?: GhOptions) =>
|
|
178
|
+
Stream.unwrap(
|
|
179
|
+
Effect.gen(function* () {
|
|
180
|
+
const query = yield* decodeOptions(watchOptions, input);
|
|
181
|
+
const gh = yield* Gh;
|
|
182
|
+
|
|
183
|
+
return gh.stream(
|
|
184
|
+
[
|
|
185
|
+
"run",
|
|
186
|
+
"watch",
|
|
187
|
+
String(query.runId),
|
|
188
|
+
"--repo",
|
|
189
|
+
query.repo,
|
|
190
|
+
"--compact",
|
|
191
|
+
"--exit-status",
|
|
192
|
+
"--interval",
|
|
193
|
+
String(query.interval ?? 3),
|
|
194
|
+
],
|
|
195
|
+
options,
|
|
196
|
+
);
|
|
197
|
+
}),
|
|
198
|
+
);
|