@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/dist/gh.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Context, Duration, Effect, Layer, Schema, Stream } from "effect";
|
|
2
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import { type GhError } from "./errors.js";
|
|
4
|
+
export interface GhOptions {
|
|
5
|
+
readonly executable?: string;
|
|
6
|
+
readonly cwd?: string;
|
|
7
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
8
|
+
/** Explicit undefined clears stdin inherited from the layer. */
|
|
9
|
+
readonly stdin?: string | Uint8Array | undefined;
|
|
10
|
+
/** Total subprocess duration, including output draining. Null disables a layer timeout. */
|
|
11
|
+
readonly timeout?: Duration.Input | null;
|
|
12
|
+
}
|
|
13
|
+
export declare const GhOutput: Schema.Struct<{
|
|
14
|
+
readonly stdout: Schema.String;
|
|
15
|
+
readonly stderr: Schema.String;
|
|
16
|
+
readonly exitCode: Schema.Int;
|
|
17
|
+
}>;
|
|
18
|
+
export interface GhOutput extends Schema.Schema.Type<typeof GhOutput> {
|
|
19
|
+
}
|
|
20
|
+
export declare const GhChunk: Schema.TaggedUnion<{
|
|
21
|
+
readonly Stderr: Schema.TaggedStruct<"Stderr", {
|
|
22
|
+
readonly text: Schema.String;
|
|
23
|
+
}>;
|
|
24
|
+
readonly Stdout: Schema.TaggedStruct<"Stdout", {
|
|
25
|
+
readonly text: Schema.String;
|
|
26
|
+
}>;
|
|
27
|
+
}>;
|
|
28
|
+
export type GhChunk = typeof GhChunk.Type;
|
|
29
|
+
export interface Interface {
|
|
30
|
+
readonly execute: (args: ReadonlyArray<string>, options?: GhOptions) => Effect.Effect<GhOutput, GhError>;
|
|
31
|
+
readonly json: <S extends Schema.Constraint>(args: ReadonlyArray<string>, schema: S, options?: GhOptions) => Effect.Effect<S["Type"], GhError, S["DecodingServices"]>;
|
|
32
|
+
readonly stream: (args: ReadonlyArray<string>, options?: GhOptions) => Stream.Stream<GhChunk, GhError>;
|
|
33
|
+
}
|
|
34
|
+
declare const Gh_base: Context.ServiceClass<Gh, "@timmo001/effect-gh/Gh", Interface>;
|
|
35
|
+
export declare class Gh extends Gh_base {
|
|
36
|
+
}
|
|
37
|
+
export declare const layer: (defaults?: GhOptions) => Layer.Layer<Gh, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
38
|
+
export {};
|
package/dist/gh.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Context, Duration, Effect, Layer, Match, Predicate, Schema, Stream, } from "effect";
|
|
2
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import { GhCommandError, GhDecodeError, GhPlatformError, GhTimeoutError, } from "./errors.js";
|
|
4
|
+
export const GhOutput = Schema.Struct({
|
|
5
|
+
stdout: Schema.String,
|
|
6
|
+
stderr: Schema.String,
|
|
7
|
+
exitCode: Schema.Int,
|
|
8
|
+
});
|
|
9
|
+
export const GhChunk = Schema.TaggedUnion({
|
|
10
|
+
Stdout: { text: Schema.String },
|
|
11
|
+
Stderr: { text: Schema.String },
|
|
12
|
+
});
|
|
13
|
+
export class Gh extends Context.Service()("@timmo001/effect-gh/Gh") {
|
|
14
|
+
}
|
|
15
|
+
const stderrLimit = 65_536;
|
|
16
|
+
export const layer = (defaults = {}) => Layer.effect(Gh, Effect.gen(function* () {
|
|
17
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
18
|
+
const open = Effect.fn("Gh.stream")(function* (args, options) {
|
|
19
|
+
const executable = options.executable ?? "gh";
|
|
20
|
+
const handle = yield* spawner
|
|
21
|
+
.spawn(ChildProcess.make(executable, args, {
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
env: {
|
|
24
|
+
...defaults.env,
|
|
25
|
+
...options.env,
|
|
26
|
+
GH_PROMPT_DISABLED: "1",
|
|
27
|
+
GH_PAGER: "cat",
|
|
28
|
+
PAGER: "cat",
|
|
29
|
+
NO_COLOR: "1",
|
|
30
|
+
CLICOLOR: "0",
|
|
31
|
+
CLICOLOR_FORCE: "0",
|
|
32
|
+
GH_FORCE_TTY: undefined,
|
|
33
|
+
GH_SPINNER_DISABLED: "1",
|
|
34
|
+
},
|
|
35
|
+
extendEnv: true,
|
|
36
|
+
shell: false,
|
|
37
|
+
stdin: options.stdin === undefined ? "ignore" : "pipe",
|
|
38
|
+
stdout: "pipe",
|
|
39
|
+
stderr: "pipe",
|
|
40
|
+
forceKillAfter: "1 second",
|
|
41
|
+
}))
|
|
42
|
+
.pipe(Effect.mapError((cause) => new GhPlatformError({ executable, cause })));
|
|
43
|
+
let stderr = "";
|
|
44
|
+
let stderrTruncated = false;
|
|
45
|
+
const output = Stream.merge(handle.stdout.pipe(Stream.decodeText(), Stream.map((text) => GhChunk.cases.Stdout.make({ text }))), handle.stderr.pipe(Stream.decodeText(), Stream.map((text) => {
|
|
46
|
+
stderrTruncated ||= stderr.length + text.length > stderrLimit;
|
|
47
|
+
stderr = (stderr + text).slice(-stderrLimit);
|
|
48
|
+
return GhChunk.cases.Stderr.make({ text });
|
|
49
|
+
}))).pipe(Stream.mapError((cause) => new GhPlatformError({ executable, cause })));
|
|
50
|
+
const completion = Effect.gen(function* () {
|
|
51
|
+
const exitCode = yield* handle.exitCode.pipe(Effect.mapError((cause) => new GhPlatformError({ executable, cause })));
|
|
52
|
+
if (exitCode !== 0) {
|
|
53
|
+
return yield* new GhCommandError({
|
|
54
|
+
executable,
|
|
55
|
+
exitCode,
|
|
56
|
+
stderr,
|
|
57
|
+
stderrTruncated,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const completed = output.pipe(Stream.concat(Stream.fromEffect(completion).pipe(Stream.drain)));
|
|
62
|
+
if (options.stdin === undefined)
|
|
63
|
+
return completed;
|
|
64
|
+
const input = Predicate.isString(options.stdin)
|
|
65
|
+
? new TextEncoder().encode(options.stdin)
|
|
66
|
+
: options.stdin;
|
|
67
|
+
return completed.pipe(Stream.mergeEffect(Stream.run(Stream.succeed(input), handle.stdin).pipe(Effect.mapError((cause) => new GhPlatformError({ executable, cause })))));
|
|
68
|
+
});
|
|
69
|
+
const stream = (args, overrides) => Stream.suspend(() => {
|
|
70
|
+
const options = { ...defaults, ...overrides };
|
|
71
|
+
const output = Stream.unwrap(open(args, options));
|
|
72
|
+
if (options.timeout == null)
|
|
73
|
+
return output;
|
|
74
|
+
if (!Duration.isFinite(Duration.fromInputUnsafe(options.timeout)))
|
|
75
|
+
return output;
|
|
76
|
+
const timeoutMs = Duration.toMillis(options.timeout);
|
|
77
|
+
return output.pipe(Stream.mergeEffect(Effect.sleep(options.timeout).pipe(Effect.andThen(Effect.fail(new GhTimeoutError({
|
|
78
|
+
executable: options.executable ?? "gh",
|
|
79
|
+
timeoutMs,
|
|
80
|
+
}))))));
|
|
81
|
+
});
|
|
82
|
+
const execute = Effect.fn("Gh.execute")(function* (args, options) {
|
|
83
|
+
return yield* stream(args, options).pipe(Stream.runFold(() => ({ stdout: "", stderr: "", exitCode: 0 }), (output, chunk) => Match.value(chunk).pipe(Match.tag("Stdout", ({ text }) => ({
|
|
84
|
+
...output,
|
|
85
|
+
stdout: output.stdout + text,
|
|
86
|
+
})), Match.tag("Stderr", ({ text }) => ({
|
|
87
|
+
...output,
|
|
88
|
+
stderr: output.stderr + text,
|
|
89
|
+
})), Match.exhaustive)));
|
|
90
|
+
});
|
|
91
|
+
const json = Effect.fn("Gh.json")(function* (args, schema, options) {
|
|
92
|
+
const output = yield* execute(args, options);
|
|
93
|
+
return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(output.stdout).pipe(Effect.mapError((cause) => new GhDecodeError({ cause })));
|
|
94
|
+
});
|
|
95
|
+
return Gh.of({ execute, json, stream });
|
|
96
|
+
}));
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Gh, GhChunk, GhOutput, layer, type GhOptions, type Interface, } from "./gh.js";
|
|
2
|
+
export { GhCommandError, GhDecodeError, GhPlatformError, GhTimeoutError, type GhError, } from "./errors.js";
|
|
3
|
+
export * as Api from "./api.js";
|
|
4
|
+
export * as Repository from "./repository.js";
|
|
5
|
+
export * as Issue from "./issue.js";
|
|
6
|
+
export * as PullRequest from "./pull-request.js";
|
|
7
|
+
export * as Workflow from "./workflow.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Gh, GhChunk, GhOutput, layer, } from "./gh.js";
|
|
2
|
+
export { GhCommandError, GhDecodeError, GhPlatformError, GhTimeoutError, } from "./errors.js";
|
|
3
|
+
export * as Api from "./api.js";
|
|
4
|
+
export * as Repository from "./repository.js";
|
|
5
|
+
export * as Issue from "./issue.js";
|
|
6
|
+
export * as PullRequest from "./pull-request.js";
|
|
7
|
+
export * as Workflow from "./workflow.js";
|
package/dist/issue.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
3
|
+
export declare const IssueSummary: Schema.Struct<{
|
|
4
|
+
readonly number: Schema.Int;
|
|
5
|
+
readonly title: Schema.String;
|
|
6
|
+
readonly url: Schema.String;
|
|
7
|
+
readonly state: Schema.Literals<readonly ["OPEN", "CLOSED"]>;
|
|
8
|
+
readonly updatedAt: Schema.String;
|
|
9
|
+
}>;
|
|
10
|
+
export interface IssueSummary extends Schema.Schema.Type<typeof IssueSummary> {
|
|
11
|
+
}
|
|
12
|
+
export declare const Issue: Schema.Struct<{
|
|
13
|
+
readonly number: Schema.Int;
|
|
14
|
+
readonly title: Schema.String;
|
|
15
|
+
readonly url: Schema.String;
|
|
16
|
+
readonly state: Schema.Literals<readonly ["OPEN", "CLOSED"]>;
|
|
17
|
+
readonly updatedAt: Schema.String;
|
|
18
|
+
readonly body: Schema.String;
|
|
19
|
+
}>;
|
|
20
|
+
export interface Issue extends Schema.Schema.Type<typeof Issue> {
|
|
21
|
+
}
|
|
22
|
+
export interface ViewOptions {
|
|
23
|
+
/** Select [HOST/]OWNER/REPO instead of the working directory's repository. */
|
|
24
|
+
readonly repo?: string;
|
|
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
|
+
declare const InvalidInput_base: Schema.Class<InvalidInput, Schema.TaggedStruct<"IssueInvalidInput", {
|
|
37
|
+
readonly cause: Schema.Defect;
|
|
38
|
+
}>, import("effect/Cause").YieldableError>;
|
|
39
|
+
export declare class InvalidInput extends InvalidInput_base {
|
|
40
|
+
}
|
|
41
|
+
/** Fetch a bounded list using gh's default state and limit unless overridden. */
|
|
42
|
+
export declare const list: (options?: ListOptions | undefined, execution?: GhOptions | undefined) => Effect.Effect<readonly {
|
|
43
|
+
readonly number: number;
|
|
44
|
+
readonly title: string;
|
|
45
|
+
readonly url: string;
|
|
46
|
+
readonly state: "CLOSED" | "OPEN";
|
|
47
|
+
readonly updatedAt: string;
|
|
48
|
+
}[], InvalidInput | import("./errors.js").GhError, Gh>;
|
|
49
|
+
/** View an issue by number or URL, including its body. */
|
|
50
|
+
export declare const view: (issue: string | number, options?: ViewOptions | undefined, execution?: GhOptions | undefined) => Effect.Effect<{
|
|
51
|
+
readonly number: number;
|
|
52
|
+
readonly title: string;
|
|
53
|
+
readonly url: string;
|
|
54
|
+
readonly state: "CLOSED" | "OPEN";
|
|
55
|
+
readonly updatedAt: string;
|
|
56
|
+
readonly body: string;
|
|
57
|
+
}, InvalidInput | import("./errors.js").GhError, Gh>;
|
|
58
|
+
export {};
|
package/dist/issue.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Effect, Predicate, Schema } from "effect";
|
|
2
|
+
import { Gh } from "./gh.js";
|
|
3
|
+
export const IssueSummary = Schema.Struct({
|
|
4
|
+
number: Schema.Int,
|
|
5
|
+
title: Schema.String,
|
|
6
|
+
url: Schema.String,
|
|
7
|
+
state: Schema.Literals(["OPEN", "CLOSED"]),
|
|
8
|
+
updatedAt: Schema.String,
|
|
9
|
+
});
|
|
10
|
+
export const Issue = Schema.Struct({
|
|
11
|
+
...IssueSummary.fields,
|
|
12
|
+
body: Schema.String,
|
|
13
|
+
});
|
|
14
|
+
export class InvalidInput extends Schema.TaggedError()("IssueInvalidInput", { cause: Schema.Defect() }) {
|
|
15
|
+
}
|
|
16
|
+
const positiveInteger = Schema.Int.check(Schema.isGreaterThan(0));
|
|
17
|
+
const summaryFields = "number,title,url,state,updatedAt";
|
|
18
|
+
/** Fetch a bounded list using gh's default state and limit unless overridden. */
|
|
19
|
+
export const list = Effect.fn("Issue.list")(function* (options = {}, execution) {
|
|
20
|
+
const args = ["issue", "list", "--json", summaryFields];
|
|
21
|
+
if (options.repo !== undefined)
|
|
22
|
+
args.push("--repo", options.repo);
|
|
23
|
+
if (options.state !== undefined)
|
|
24
|
+
args.push("--state", options.state);
|
|
25
|
+
if (options.limit !== undefined) {
|
|
26
|
+
const limit = yield* Schema.decodeEffect(positiveInteger)(options.limit).pipe(Effect.mapError((cause) => new InvalidInput({ cause })));
|
|
27
|
+
args.push("--limit", String(limit));
|
|
28
|
+
}
|
|
29
|
+
for (const label of options.labels ?? [])
|
|
30
|
+
args.push("--label", label);
|
|
31
|
+
if (options.assignee !== undefined)
|
|
32
|
+
args.push("--assignee", options.assignee);
|
|
33
|
+
if (options.search !== undefined)
|
|
34
|
+
args.push("--search", options.search);
|
|
35
|
+
const gh = yield* Gh;
|
|
36
|
+
return yield* gh.json(args, Schema.Array(IssueSummary), execution);
|
|
37
|
+
});
|
|
38
|
+
/** View an issue by number or URL, including its body. */
|
|
39
|
+
export const view = Effect.fn("Issue.view")(function* (issue, options = {}, execution) {
|
|
40
|
+
if (Predicate.isNumber(issue)) {
|
|
41
|
+
yield* Schema.decodeEffect(positiveInteger)(issue).pipe(Effect.mapError((cause) => new InvalidInput({ cause })));
|
|
42
|
+
}
|
|
43
|
+
const gh = yield* Gh;
|
|
44
|
+
const args = ["issue", "view", "--json", `${summaryFields},body`];
|
|
45
|
+
if (options.repo !== undefined)
|
|
46
|
+
args.push("--repo", options.repo);
|
|
47
|
+
args.push("--", String(issue));
|
|
48
|
+
return yield* gh.json(args, Issue, execution);
|
|
49
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { GhDecodeError } from "./errors.js";
|
|
3
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
4
|
+
export declare const Summary: Schema.Struct<{
|
|
5
|
+
readonly number: Schema.Int;
|
|
6
|
+
readonly title: Schema.String;
|
|
7
|
+
readonly url: Schema.String;
|
|
8
|
+
readonly state: Schema.Literals<readonly ["OPEN", "CLOSED", "MERGED"]>;
|
|
9
|
+
readonly isDraft: Schema.Boolean;
|
|
10
|
+
readonly headRefName: Schema.String;
|
|
11
|
+
readonly headRefOid: Schema.String;
|
|
12
|
+
}>;
|
|
13
|
+
export interface Summary extends Schema.Schema.Type<typeof Summary> {
|
|
14
|
+
}
|
|
15
|
+
export declare const Check: Schema.Struct<{
|
|
16
|
+
readonly name: Schema.String;
|
|
17
|
+
readonly state: Schema.String;
|
|
18
|
+
readonly bucket: Schema.Literals<readonly ["pass", "fail", "pending", "skipping", "cancel"]>;
|
|
19
|
+
readonly link: Schema.NullOr<Schema.String>;
|
|
20
|
+
readonly workflow: Schema.NullOr<Schema.String>;
|
|
21
|
+
}>;
|
|
22
|
+
export interface Check extends Schema.Schema.Type<typeof Check> {
|
|
23
|
+
}
|
|
24
|
+
export declare const ChecksResult: Schema.Struct<{
|
|
25
|
+
readonly checks: Schema.$Array<Schema.Struct<{
|
|
26
|
+
readonly name: Schema.String;
|
|
27
|
+
readonly state: Schema.String;
|
|
28
|
+
readonly bucket: Schema.Literals<readonly ["pass", "fail", "pending", "skipping", "cancel"]>;
|
|
29
|
+
readonly link: Schema.NullOr<Schema.String>;
|
|
30
|
+
readonly workflow: Schema.NullOr<Schema.String>;
|
|
31
|
+
}>>;
|
|
32
|
+
readonly status: Schema.Literals<readonly ["success", "failure", "pending"]>;
|
|
33
|
+
readonly exitCode: Schema.Literals<readonly [0, 1, 8]>;
|
|
34
|
+
}>;
|
|
35
|
+
export interface ChecksResult extends Schema.Schema.Type<typeof ChecksResult> {
|
|
36
|
+
}
|
|
37
|
+
export interface Options extends GhOptions {
|
|
38
|
+
readonly repository: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ListOptions extends Options {
|
|
41
|
+
readonly state?: "open" | "closed" | "merged" | "all";
|
|
42
|
+
readonly limit?: number;
|
|
43
|
+
readonly base?: string;
|
|
44
|
+
readonly head?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ChecksOptions extends Options {
|
|
47
|
+
readonly required?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare const list: (options: ListOptions) => Effect.Effect<readonly {
|
|
50
|
+
readonly number: number;
|
|
51
|
+
readonly title: string;
|
|
52
|
+
readonly url: string;
|
|
53
|
+
readonly state: "CLOSED" | "MERGED" | "OPEN";
|
|
54
|
+
readonly isDraft: boolean;
|
|
55
|
+
readonly headRefName: string;
|
|
56
|
+
readonly headRefOid: string;
|
|
57
|
+
}[], import("./errors.js").GhError, Gh>;
|
|
58
|
+
export declare const view: (selector: string | number, options: Options) => Effect.Effect<{
|
|
59
|
+
readonly number: number;
|
|
60
|
+
readonly title: string;
|
|
61
|
+
readonly url: string;
|
|
62
|
+
readonly state: "CLOSED" | "MERGED" | "OPEN";
|
|
63
|
+
readonly isDraft: boolean;
|
|
64
|
+
readonly headRefName: string;
|
|
65
|
+
readonly headRefOid: string;
|
|
66
|
+
}, import("./errors.js").GhError, Gh>;
|
|
67
|
+
/** Failed and pending checks are data; invalid output retains the command error. */
|
|
68
|
+
export declare const checks: (selector: string | number, options: ChecksOptions) => Effect.Effect<{
|
|
69
|
+
readonly checks: readonly {
|
|
70
|
+
readonly name: string;
|
|
71
|
+
readonly state: string;
|
|
72
|
+
readonly bucket: "cancel" | "fail" | "pass" | "pending" | "skipping";
|
|
73
|
+
readonly link: string | null;
|
|
74
|
+
readonly workflow: string | null;
|
|
75
|
+
}[];
|
|
76
|
+
readonly status: "failure" | "pending" | "success";
|
|
77
|
+
readonly exitCode: 0 | 1 | 8;
|
|
78
|
+
}, import("./errors.js").GhCommandError | GhDecodeError | import("./errors.js").GhPlatformError | import("./errors.js").GhTimeoutError, Gh>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Effect, Match, Predicate, Schema, Stream } from "effect";
|
|
2
|
+
import { GhDecodeError } from "./errors.js";
|
|
3
|
+
import { Gh } from "./gh.js";
|
|
4
|
+
export const Summary = Schema.Struct({
|
|
5
|
+
number: Schema.Int,
|
|
6
|
+
title: Schema.String,
|
|
7
|
+
url: Schema.String,
|
|
8
|
+
state: Schema.Literals(["OPEN", "CLOSED", "MERGED"]),
|
|
9
|
+
isDraft: Schema.Boolean,
|
|
10
|
+
headRefName: Schema.String,
|
|
11
|
+
headRefOid: Schema.String,
|
|
12
|
+
});
|
|
13
|
+
export const Check = Schema.Struct({
|
|
14
|
+
name: Schema.String,
|
|
15
|
+
state: Schema.String,
|
|
16
|
+
bucket: Schema.Literals(["pass", "fail", "pending", "skipping", "cancel"]),
|
|
17
|
+
link: Schema.NullOr(Schema.String),
|
|
18
|
+
workflow: Schema.NullOr(Schema.String),
|
|
19
|
+
});
|
|
20
|
+
export const ChecksResult = Schema.Struct({
|
|
21
|
+
checks: Schema.Array(Check),
|
|
22
|
+
status: Schema.Literals(["success", "failure", "pending"]),
|
|
23
|
+
exitCode: Schema.Literals([0, 1, 8]),
|
|
24
|
+
});
|
|
25
|
+
const fields = "number,title,url,state,isDraft,headRefName,headRefOid";
|
|
26
|
+
export const list = Effect.fn("PullRequest.list")(function* (options) {
|
|
27
|
+
const gh = yield* Gh;
|
|
28
|
+
const args = [
|
|
29
|
+
"pr",
|
|
30
|
+
"list",
|
|
31
|
+
`--repo=${options.repository}`,
|
|
32
|
+
`--state=${options.state ?? "open"}`,
|
|
33
|
+
`--limit=${options.limit ?? 30}`,
|
|
34
|
+
`--json=${fields}`,
|
|
35
|
+
];
|
|
36
|
+
if (options.base !== undefined)
|
|
37
|
+
args.push(`--base=${options.base}`);
|
|
38
|
+
if (options.head !== undefined)
|
|
39
|
+
args.push(`--head=${options.head}`);
|
|
40
|
+
return yield* gh.json(args, Schema.Array(Summary), options);
|
|
41
|
+
});
|
|
42
|
+
export const view = Effect.fn("PullRequest.view")(function* (selector, options) {
|
|
43
|
+
const gh = yield* Gh;
|
|
44
|
+
return yield* gh.json([
|
|
45
|
+
"pr",
|
|
46
|
+
"view",
|
|
47
|
+
`--repo=${options.repository}`,
|
|
48
|
+
`--json=${fields}`,
|
|
49
|
+
"--",
|
|
50
|
+
String(selector),
|
|
51
|
+
], Summary, options);
|
|
52
|
+
});
|
|
53
|
+
/** Failed and pending checks are data; invalid output retains the command error. */
|
|
54
|
+
export const checks = Effect.fn("PullRequest.checks")(function* (selector, options) {
|
|
55
|
+
const gh = yield* Gh;
|
|
56
|
+
const args = [
|
|
57
|
+
"pr",
|
|
58
|
+
"checks",
|
|
59
|
+
`--repo=${options.repository}`,
|
|
60
|
+
"--json=name,state,bucket,link,workflow",
|
|
61
|
+
];
|
|
62
|
+
if (options.required)
|
|
63
|
+
args.push("--required");
|
|
64
|
+
args.push("--", String(selector));
|
|
65
|
+
let stdout = "";
|
|
66
|
+
const commandError = yield* gh.stream(args, options).pipe(Stream.runForEach((chunk) => Effect.sync(() => {
|
|
67
|
+
if (Predicate.isTagged(chunk, "Stdout"))
|
|
68
|
+
stdout += chunk.text;
|
|
69
|
+
})), Effect.as(undefined), Effect.catchTag("GhCommandError", (error) => error.exitCode === 1 || error.exitCode === 8
|
|
70
|
+
? Effect.succeed(error)
|
|
71
|
+
: Effect.fail(error)));
|
|
72
|
+
const decoded = yield* Schema.decodeEffect(Schema.fromJsonString(Schema.Array(Check)))(stdout).pipe(Effect.mapError((cause) => commandError ?? new GhDecodeError({ cause })));
|
|
73
|
+
const exitCode = commandError?.exitCode === 8 ? 8 : commandError ? 1 : 0;
|
|
74
|
+
return ChecksResult.make({
|
|
75
|
+
checks: decoded,
|
|
76
|
+
status: Match.value(exitCode).pipe(Match.when(8, () => "pending"), Match.when(1, () => "failure"), Match.when(0, () => "success"), Match.exhaustive),
|
|
77
|
+
exitCode,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
3
|
+
export declare const Repository: Schema.Struct<{
|
|
4
|
+
readonly nameWithOwner: Schema.String;
|
|
5
|
+
readonly url: Schema.String;
|
|
6
|
+
readonly defaultBranchRef: Schema.NullOr<Schema.Struct<{
|
|
7
|
+
readonly name: Schema.String;
|
|
8
|
+
}>>;
|
|
9
|
+
readonly isPrivate: Schema.Boolean;
|
|
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 declare const view: (repo?: string | undefined, options?: GhOptions | undefined) => Effect.Effect<{
|
|
15
|
+
readonly nameWithOwner: string;
|
|
16
|
+
readonly url: string;
|
|
17
|
+
readonly defaultBranchRef: {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
} | null;
|
|
20
|
+
readonly isPrivate: boolean;
|
|
21
|
+
}, import("./errors.js").GhError, Gh>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Gh } from "./gh.js";
|
|
3
|
+
export const Repository = Schema.Struct({
|
|
4
|
+
nameWithOwner: Schema.String,
|
|
5
|
+
url: Schema.String,
|
|
6
|
+
defaultBranchRef: Schema.NullOr(Schema.Struct({ name: Schema.String })),
|
|
7
|
+
isPrivate: Schema.Boolean,
|
|
8
|
+
});
|
|
9
|
+
/** View [HOST/]OWNER/REPO, or the repository selected by the working directory. */
|
|
10
|
+
export const view = Effect.fn("Repository.view")(function* (repo, options) {
|
|
11
|
+
const gh = yield* Gh;
|
|
12
|
+
const args = [
|
|
13
|
+
"repo",
|
|
14
|
+
"view",
|
|
15
|
+
"--json",
|
|
16
|
+
"nameWithOwner,url,defaultBranchRef,isPrivate",
|
|
17
|
+
];
|
|
18
|
+
if (repo !== undefined)
|
|
19
|
+
args.push("--", repo);
|
|
20
|
+
return yield* gh.json(args, Repository, options);
|
|
21
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { Gh, type GhOptions } from "./gh.js";
|
|
3
|
+
export declare const Run: Schema.Struct<{
|
|
4
|
+
readonly databaseId: Schema.Int;
|
|
5
|
+
readonly attempt: Schema.Int;
|
|
6
|
+
readonly headBranch: Schema.NullOr<Schema.String>;
|
|
7
|
+
readonly headSha: Schema.String;
|
|
8
|
+
readonly status: Schema.String;
|
|
9
|
+
readonly conclusion: Schema.NullOr<Schema.String>;
|
|
10
|
+
readonly url: Schema.String;
|
|
11
|
+
readonly workflowName: Schema.NullOr<Schema.String>;
|
|
12
|
+
}>;
|
|
13
|
+
export interface Run extends Schema.Schema.Type<typeof Run> {
|
|
14
|
+
}
|
|
15
|
+
declare const listOptions: Schema.Struct<{
|
|
16
|
+
readonly repo: Schema.String;
|
|
17
|
+
readonly limit: Schema.optionalKey<Schema.Int>;
|
|
18
|
+
readonly branch: Schema.optionalKey<Schema.NonEmptyString>;
|
|
19
|
+
readonly commit: Schema.optionalKey<Schema.NonEmptyString>;
|
|
20
|
+
readonly workflow: Schema.optionalKey<Schema.NonEmptyString>;
|
|
21
|
+
readonly status: Schema.optionalKey<Schema.Literals<readonly ["queued", "completed", "in_progress", "requested", "waiting", "pending", "action_required", "cancelled", "failure", "neutral", "skipped", "stale", "startup_failure", "success", "timed_out"]>>;
|
|
22
|
+
}>;
|
|
23
|
+
export interface ListOptions extends Schema.Schema.Type<typeof listOptions> {
|
|
24
|
+
}
|
|
25
|
+
declare const viewOptions: Schema.Struct<{
|
|
26
|
+
readonly repo: Schema.String;
|
|
27
|
+
readonly runId: Schema.Int;
|
|
28
|
+
readonly attempt: Schema.optionalKey<Schema.Int>;
|
|
29
|
+
}>;
|
|
30
|
+
export interface ViewOptions extends Schema.Schema.Type<typeof viewOptions> {
|
|
31
|
+
}
|
|
32
|
+
declare const logsOptions: Schema.Struct<{
|
|
33
|
+
readonly repo: Schema.String;
|
|
34
|
+
readonly runId: Schema.Int;
|
|
35
|
+
readonly attempt: Schema.optionalKey<Schema.Int>;
|
|
36
|
+
readonly failedOnly: Schema.optionalKey<Schema.Boolean>;
|
|
37
|
+
readonly job: Schema.optionalKey<Schema.Int>;
|
|
38
|
+
}>;
|
|
39
|
+
export interface LogsOptions extends Schema.Schema.Type<typeof logsOptions> {
|
|
40
|
+
}
|
|
41
|
+
declare const watchOptions: Schema.Struct<{
|
|
42
|
+
readonly repo: Schema.String;
|
|
43
|
+
readonly runId: Schema.Int;
|
|
44
|
+
readonly interval: Schema.optionalKey<Schema.Int>;
|
|
45
|
+
}>;
|
|
46
|
+
export interface WatchOptions extends Schema.Schema.Type<typeof watchOptions> {
|
|
47
|
+
}
|
|
48
|
+
declare const InvalidOptions_base: Schema.Class<InvalidOptions, Schema.TaggedStruct<"WorkflowInvalidOptions", {
|
|
49
|
+
readonly cause: Schema.Defect;
|
|
50
|
+
}>, import("effect/Cause").YieldableError>;
|
|
51
|
+
export declare class InvalidOptions extends InvalidOptions_base {
|
|
52
|
+
}
|
|
53
|
+
/** Fetch at most `limit` recent runs (default 20), without unbounded pagination. */
|
|
54
|
+
export declare const list: (input: ListOptions, options?: GhOptions | undefined) => Effect.Effect<readonly {
|
|
55
|
+
readonly databaseId: number;
|
|
56
|
+
readonly attempt: number;
|
|
57
|
+
readonly headBranch: string | null;
|
|
58
|
+
readonly headSha: string;
|
|
59
|
+
readonly status: string;
|
|
60
|
+
readonly conclusion: string | null;
|
|
61
|
+
readonly url: string;
|
|
62
|
+
readonly workflowName: string | null;
|
|
63
|
+
}[], InvalidOptions | import("./errors.js").GhError, Gh>;
|
|
64
|
+
/** View the latest attempt unless an explicit attempt is supplied. */
|
|
65
|
+
export declare const view: (input: ViewOptions, options?: GhOptions | undefined) => Effect.Effect<{
|
|
66
|
+
readonly databaseId: number;
|
|
67
|
+
readonly attempt: number;
|
|
68
|
+
readonly headBranch: string | null;
|
|
69
|
+
readonly headSha: string;
|
|
70
|
+
readonly status: string;
|
|
71
|
+
readonly conclusion: string | null;
|
|
72
|
+
readonly url: string;
|
|
73
|
+
readonly workflowName: string | null;
|
|
74
|
+
}, InvalidOptions | import("./errors.js").GhError, Gh>;
|
|
75
|
+
/** Fetch full logs or failed steps, optionally for one attempt and job. */
|
|
76
|
+
export declare const logs: (input: LogsOptions, options?: GhOptions | undefined) => Effect.Effect<string, InvalidOptions | import("./errors.js").GhError, Gh>;
|
|
77
|
+
/**
|
|
78
|
+
* Stream native CLI progress, failing if the run fails. Requires gh with
|
|
79
|
+
* `--compact` support; gh watch cannot authenticate with fine-grained PATs.
|
|
80
|
+
* Inherits the core timeout; pass `{ timeout: null }` to disable it.
|
|
81
|
+
*/
|
|
82
|
+
export declare const watch: (input: WatchOptions, options?: GhOptions) => Stream.Stream<{
|
|
83
|
+
readonly _tag: "Stderr";
|
|
84
|
+
readonly text: string;
|
|
85
|
+
} | {
|
|
86
|
+
readonly _tag: "Stdout";
|
|
87
|
+
readonly text: string;
|
|
88
|
+
}, InvalidOptions | import("./errors.js").GhError, Gh>;
|
|
89
|
+
export {};
|