@kud/gh 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/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @kud/gh
2
+
3
+ Surface-agnostic GitHub core. Shells out to the `gh` CLI and returns plain data —
4
+ consumed by CLIs, Ink dashboards, and MCP servers alike. No UI, no framework.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @kud/gh
10
+ ```
11
+
12
+ Requires the [`gh`](https://cli.github.com) CLI, authenticated (`gh auth login`).
13
+
14
+ ## API
15
+
16
+ ### Transport primitives
17
+
18
+ ```ts
19
+ import { ghGraphql, ghRest } from "@kud/gh"
20
+
21
+ const data = await ghGraphql<T>(query, { owner: "kud", name: "gh", number: 1 })
22
+ const raw = await ghRest("repos/kud/gh/pulls/1/comments", {
23
+ method: "POST",
24
+ fields: { body: "hi", in_reply_to: 123 },
25
+ })
26
+ ```
27
+
28
+ `ghGraphql` returns the unwrapped `data` field; `ghRest` returns raw stdout.
29
+ String fields pass as `-f`, numbers as `-F` (typed-field), matching `gh api`.
30
+
31
+ ### PR review comments
32
+
33
+ ```ts
34
+ import {
35
+ fetchPrComments,
36
+ replyToThread,
37
+ resolveThread,
38
+ unresolveThread,
39
+ } from "@kud/gh"
40
+
41
+ const { headRef, conversation, threads } = await fetchPrComments("kud", "gh", 1)
42
+ await replyToThread({
43
+ repo: "kud/gh",
44
+ pull: 1,
45
+ inReplyTo: 123,
46
+ body: "thanks!",
47
+ })
48
+ await resolveThread(thread.id) // → boolean isResolved
49
+ await unresolveThread(thread.id)
50
+ ```
51
+
52
+ Types: `PrComments`, `ReviewThread`, `Comment`, `ReplyToThreadOptions`.
53
+
54
+ ## Development
55
+
56
+ ```sh
57
+ npm run typecheck
58
+ npm run test
59
+ npm run build
60
+ ```
61
+
62
+ ## Licence
63
+
64
+ MIT © Erwann Mest
@@ -0,0 +1,4 @@
1
+ export declare const mergePr: (repo: string, number: number) => Promise<void>;
2
+ export declare const rerunFailedRun: (repo: string, runId: string) => Promise<void>;
3
+ export declare const reRequestReviewer: (repo: string, number: number, login: string) => Promise<void>;
4
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,GAAU,MAAM,MAAM,EAAE,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAExE,CAAA;AAGD,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,OAAO,MAAM,KACZ,OAAO,CAAC,IAAI,CAEd,CAAA;AAGD,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,OAAO,MAAM,KACZ,OAAO,CAAC,IAAI,CAUd,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { execa } from "execa";
2
+ // PR write-actions — the mutating gh operations a health panel offers. Kept in
3
+ // the core (not the UI) so any surface can invoke them; they shell out to `gh`
4
+ // and resolve when the command completes.
5
+ // Merge a pull request with a merge commit.
6
+ export const mergePr = async (repo, number) => {
7
+ await execa("gh", ["pr", "merge", String(number), "--repo", repo, "--merge"]);
8
+ };
9
+ // Re-run only the failed jobs of a GitHub Actions run.
10
+ export const rerunFailedRun = async (repo, runId) => {
11
+ await execa("gh", ["run", "rerun", runId, "--repo", repo, "--failed"]);
12
+ };
13
+ // Re-request a review by adding the reviewer back onto the PR.
14
+ export const reRequestReviewer = async (repo, number, login) => {
15
+ await execa("gh", [
16
+ "pr",
17
+ "edit",
18
+ String(number),
19
+ "--repo",
20
+ repo,
21
+ "--add-reviewer",
22
+ login,
23
+ ]);
24
+ };
25
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAE7B,+EAA+E;AAC/E,+EAA+E;AAC/E,0CAA0C;AAE1C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAAE,IAAY,EAAE,MAAc,EAAiB,EAAE;IAC3E,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAA;AAC/E,CAAC,CAAA;AAED,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,IAAY,EACZ,KAAa,EACE,EAAE;IACjB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAA;AACxE,CAAC,CAAA;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,IAAY,EACZ,MAAc,EACd,KAAa,EACE,EAAE;IACjB,MAAM,KAAK,CAAC,IAAI,EAAE;QAChB,IAAI;QACJ,MAAM;QACN,MAAM,CAAC,MAAM,CAAC;QACd,QAAQ;QACR,IAAI;QACJ,gBAAgB;QAChB,KAAK;KACN,CAAC,CAAA;AACJ,CAAC,CAAA"}
package/dist/gh.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type GhFieldValue = string | number;
2
+ export type GhFields = Record<string, GhFieldValue>;
3
+ export declare const ghGraphql: <T>(query: string, vars?: GhFields) => Promise<T>;
4
+ export type GhRestMethod = "GET" | "POST" | "PATCH" | "DELETE";
5
+ export type GhRestOptions = {
6
+ method?: GhRestMethod;
7
+ fields?: GhFields;
8
+ };
9
+ export declare const ghRest: (endpoint: string, options?: GhRestOptions) => Promise<string>;
10
+ //# sourceMappingURL=gh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh.d.ts","sourceRoot":"","sources":["../src/gh.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AASnD,eAAO,MAAM,SAAS,GAAU,CAAC,EAC/B,OAAO,MAAM,EACb,OAAM,QAAa,KAClB,OAAO,CAAC,CAAC,CAUX,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE9D,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,MAAM,GACjB,UAAU,MAAM,EAChB,UAAS,aAAkB,KAC1B,OAAO,CAAC,MAAM,CAYhB,CAAA"}
package/dist/gh.js ADDED
@@ -0,0 +1,26 @@
1
+ import { execa } from "execa";
2
+ const toFieldArgs = (fields) => Object.entries(fields).flatMap(([key, value]) => typeof value === "number"
3
+ ? ["-F", `${key}=${value}`]
4
+ : ["-f", `${key}=${value}`]);
5
+ export const ghGraphql = async (query, vars = {}) => {
6
+ const { stdout } = await execa("gh", [
7
+ "api",
8
+ "graphql",
9
+ "-f",
10
+ `query=${query}`,
11
+ ...toFieldArgs(vars),
12
+ ]);
13
+ return JSON.parse(stdout).data;
14
+ };
15
+ export const ghRest = async (endpoint, options = {}) => {
16
+ const { method, fields = {} } = options;
17
+ const methodArgs = method ? ["-X", method] : [];
18
+ const { stdout } = await execa("gh", [
19
+ "api",
20
+ endpoint,
21
+ ...methodArgs,
22
+ ...toFieldArgs(fields),
23
+ ]);
24
+ return stdout;
25
+ };
26
+ //# sourceMappingURL=gh.js.map
package/dist/gh.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh.js","sourceRoot":"","sources":["../src/gh.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAM7B,MAAM,WAAW,GAAG,CAAC,MAAgB,EAAY,EAAE,CACjD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAC9C,OAAO,KAAK,KAAK,QAAQ;IACvB,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAC9B,CAAA;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,KAAa,EACb,OAAiB,EAAE,EACP,EAAE;IACd,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE;QACnC,KAAK;QACL,SAAS;QACT,IAAI;QACJ,SAAS,KAAK,EAAE;QAChB,GAAG,WAAW,CAAC,IAAI,CAAC;KACrB,CAAC,CAAA;IAEF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAS,CAAA;AACrC,CAAC,CAAA;AASD,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,EACzB,QAAgB,EAChB,UAAyB,EAAE,EACV,EAAE;IACnB,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,OAAO,CAAA;IACvC,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE/C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE;QACnC,KAAK;QACL,QAAQ;QACR,GAAG,UAAU;QACb,GAAG,WAAW,CAAC,MAAM,CAAC;KACvB,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
@@ -0,0 +1,43 @@
1
+ export type Health = "ci-fail" | "conflict" | "changes-req" | "threads" | "pending" | "approved" | "waiting" | "draft" | "merged" | "closed" | "none";
2
+ export type PrCheck = {
3
+ name?: string;
4
+ context?: string;
5
+ status?: string;
6
+ conclusion?: string | null;
7
+ state?: string;
8
+ detailsUrl?: string;
9
+ targetUrl?: string;
10
+ workflowName?: string;
11
+ databaseId?: number;
12
+ };
13
+ export type PrReview = {
14
+ author: {
15
+ login: string;
16
+ } | null;
17
+ state: string;
18
+ };
19
+ export type PrHealthData = {
20
+ statusCheckRollup: PrCheck[];
21
+ reviews: PrReview[];
22
+ reviewDecision: string | null;
23
+ mergeable: string;
24
+ mergeStateStatus: string;
25
+ author: {
26
+ login: string;
27
+ } | null;
28
+ };
29
+ export declare const isFailCheck: (c: PrCheck) => boolean;
30
+ export declare const isPendingCheck: (c: PrCheck) => boolean;
31
+ export declare const isPassCheck: (c: PrCheck) => boolean;
32
+ export declare const latestChecks: (checks: PrCheck[]) => PrCheck[];
33
+ export type ComputeHealthInput = {
34
+ state?: string;
35
+ isDraft?: boolean;
36
+ checks: PrCheck[];
37
+ mergeable?: string;
38
+ reviewDecision?: string | null;
39
+ unresolvedThreads?: number;
40
+ };
41
+ export declare const computeHealth: (input: ComputeHealthInput) => Health;
42
+ export declare const fetchHealth: (repo: string, number: number) => Promise<PrHealthData>;
43
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,MAAM,GACd,SAAS,GACT,UAAU,GACV,aAAa,GACb,SAAS,GACT,SAAS,GACT,UAAU,GACV,SAAS,GACT,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,CAAA;AAMV,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IAAE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAI1E,MAAM,MAAM,YAAY,GAAG;IACzB,iBAAiB,EAAE,OAAO,EAAE,CAAA;IAC5B,OAAO,EAAE,QAAQ,EAAE,CAAA;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACjC,CAAA;AAMD,eAAO,MAAM,WAAW,GAAI,GAAG,OAAO,KAAG,OAKpB,CAAA;AAErB,eAAO,MAAM,cAAc,GAAI,GAAG,OAAO,KAAG,OACS,CAAA;AAErD,eAAO,MAAM,WAAW,GAAI,GAAG,OAAO,KAAG,OAIf,CAAA;AAM1B,eAAO,MAAM,YAAY,GAAI,QAAQ,OAAO,EAAE,KAAG,OAAO,EAavD,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAE/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,OAAO,EAAE,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAMD,eAAO,MAAM,aAAa,GAAI,OAAO,kBAAkB,KAAG,MAkBzD,CAAA;AAID,eAAO,MAAM,WAAW,GACtB,MAAM,MAAM,EACZ,QAAQ,MAAM,KACb,OAAO,CAAC,YAAY,CAWtB,CAAA"}
package/dist/health.js ADDED
@@ -0,0 +1,75 @@
1
+ import { execa } from "execa";
2
+ // The canonical check classifiers — one source of truth, unifying the two
3
+ // hand-maintained copies that had drifted (glance vs panel). A check is failing
4
+ // on any terminal-bad conclusion or a bad StatusContext state; pending while it
5
+ // is still running; passing otherwise (an explicit success signal).
6
+ export const isFailCheck = (c) => ["FAILURE", "TIMED_OUT", "CANCELLED", "ACTION_REQUIRED"].includes(c.conclusion ?? "") ||
7
+ c.state === "FAILURE" ||
8
+ c.state === "ERROR";
9
+ export const isPendingCheck = (c) => c.status === "IN_PROGRESS" || c.state === "PENDING";
10
+ export const isPassCheck = (c) => !isFailCheck(c) &&
11
+ !isPendingCheck(c) &&
12
+ (["SUCCESS", "NEUTRAL", "SKIPPED"].includes(c.conclusion ?? "") ||
13
+ c.state === "SUCCESS");
14
+ // Collapse a rollup to the latest run per check name and drop SKIPPED ones: a
15
+ // re-run pushes a fresh CheckRun with a higher databaseId, so an older failure
16
+ // must not outvote the current pass. StatusContexts (no databaseId) keep their
17
+ // first occurrence.
18
+ export const latestChecks = (checks) => {
19
+ const latestByKey = new Map();
20
+ for (const c of checks) {
21
+ const key = c.name ?? c.context ?? "";
22
+ const existing = latestByKey.get(key);
23
+ if (!existing ||
24
+ (c.databaseId != null &&
25
+ (existing.databaseId == null || c.databaseId > existing.databaseId)))
26
+ latestByKey.set(key, c);
27
+ }
28
+ return [...latestByKey.values()].filter((c) => c.conclusion !== "SKIPPED");
29
+ };
30
+ // Derive the single health token. Precedence is deliberate: terminal states
31
+ // first, then the urgent actionable states, then unresolved threads (which
32
+ // outrank the passive pending/approved/waiting — an otherwise-quiet PR with open
33
+ // threads still needs your reply), then the passive states.
34
+ export const computeHealth = (input) => {
35
+ if (input.state === "MERGED")
36
+ return "merged";
37
+ if (input.state === "CLOSED")
38
+ return "closed";
39
+ if (input.isDraft === undefined)
40
+ return "none";
41
+ if (input.isDraft)
42
+ return "draft";
43
+ const active = latestChecks(input.checks);
44
+ const hasFail = active.some(isFailCheck);
45
+ const hasPending = active.some(isPendingCheck);
46
+ const unresolvedThreads = input.unresolvedThreads ?? 0;
47
+ if (hasFail)
48
+ return "ci-fail";
49
+ if (input.mergeable === "CONFLICTING")
50
+ return "conflict";
51
+ if (input.reviewDecision === "CHANGES_REQUESTED")
52
+ return "changes-req";
53
+ if (unresolvedThreads > 0)
54
+ return "threads";
55
+ if (hasPending)
56
+ return "pending";
57
+ if (input.reviewDecision === "APPROVED")
58
+ return "approved";
59
+ return "waiting";
60
+ };
61
+ // Fetch the detail-panel payload for one PR via the gh CLI. Uses `gh pr view`
62
+ // (not `gh api`) for the ready-shaped `--json` projection a panel renders.
63
+ export const fetchHealth = async (repo, number) => {
64
+ const { stdout } = await execa("gh", [
65
+ "pr",
66
+ "view",
67
+ String(number),
68
+ "--repo",
69
+ repo,
70
+ "--json",
71
+ "statusCheckRollup,reviews,reviewDecision,mergeable,mergeStateStatus,author",
72
+ ]);
73
+ return JSON.parse(stdout);
74
+ };
75
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export * from "./gh.js";
2
+ export * from "./pr.js";
3
+ export * from "./pr-comments.js";
4
+ export * from "./health.js";
5
+ export * from "./actions.js";
6
+ export * from "./webhook.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ // @kud/gh — surface-agnostic GitHub core. Per-concern domain modules over the
2
+ // `gh` CLI. Every export is UI-agnostic: it shells out to `gh` and returns plain
3
+ // data, so a CLI, an Ink dashboard, or an MCP server all consume it alike.
4
+ //
5
+ // Transport policy — two ways to reach GitHub through `gh`, chosen per need:
6
+ // • `gh <cmd> --json` (porcelain) for single-resource reads, branch→PR/repo
7
+ // resolution, and workflow actions (merge/rerun/re-request) — simpler, and
8
+ // gh already encapsulates the resolution and merge-method/branch logic.
9
+ // • `gh api` GraphQL/REST (ghGraphql/ghRest) when the query must be shaped or
10
+ // optimised — aggregations, precise nested selections, or operations with no
11
+ // porcelain equivalent (review-thread resolve/reply, webhook delivery replay).
12
+ // The deciding test is "is there a query to optimise?": yes → gh api; no →
13
+ // porcelain.
14
+ export * from "./gh.js";
15
+ export * from "./pr.js";
16
+ export * from "./pr-comments.js";
17
+ export * from "./health.js";
18
+ export * from "./actions.js";
19
+ export * from "./webhook.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,iFAAiF;AACjF,2EAA2E;AAC3E,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,4EAA4E;AAC5E,gFAAgF;AAChF,iFAAiF;AACjF,mFAAmF;AACnF,2EAA2E;AAC3E,aAAa;AACb,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA"}
@@ -0,0 +1,34 @@
1
+ export type Comment = {
2
+ author: string;
3
+ body: string;
4
+ databaseId?: number;
5
+ url?: string;
6
+ path?: string | null;
7
+ diffHunk?: string;
8
+ createdAt?: string;
9
+ };
10
+ export type ReviewThread = {
11
+ id: string;
12
+ path: string | null;
13
+ line: number | null;
14
+ originalLine: number | null;
15
+ isResolved: boolean;
16
+ isOutdated: boolean;
17
+ comments: Comment[];
18
+ };
19
+ export type PrComments = {
20
+ headRef: string;
21
+ conversation: Comment[];
22
+ threads: ReviewThread[];
23
+ };
24
+ export type ReplyToThreadOptions = {
25
+ repo: string;
26
+ pull: number;
27
+ inReplyTo: number;
28
+ body: string;
29
+ };
30
+ export declare const fetchPrComments: (owner: string, name: string, number: number) => Promise<PrComments>;
31
+ export declare const replyToThread: (options: ReplyToThreadOptions) => Promise<void>;
32
+ export declare const resolveThread: (threadId: string) => Promise<boolean>;
33
+ export declare const unresolveThread: (threadId: string) => Promise<boolean>;
34
+ //# sourceMappingURL=pr-comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-comments.d.ts","sourceRoot":"","sources":["../src/pr-comments.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,OAAO,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,OAAO,EAAE,CAAA;IACvB,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AA8GD,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,EACb,MAAM,MAAM,EACZ,QAAQ,MAAM,KACb,OAAO,CAAC,UAAU,CAqBpB,CAAA;AAED,eAAO,MAAM,aAAa,GACxB,SAAS,oBAAoB,KAC5B,OAAO,CAAC,IAAI,CAId,CAAA;AAED,eAAO,MAAM,aAAa,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CAKrE,CAAA;AAED,eAAO,MAAM,eAAe,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CAMvE,CAAA"}
@@ -0,0 +1,88 @@
1
+ import { ghGraphql, ghRest } from "./gh.js";
2
+ const FETCH_PR_COMMENTS_QUERY = `
3
+ query($owner:String!, $name:String!, $number:Int!) {
4
+ repository(owner:$owner, name:$name) {
5
+ pullRequest(number:$number) {
6
+ headRefName
7
+ comments(first: 50) { nodes { author { login } body } }
8
+ reviewThreads(first: 100) {
9
+ nodes {
10
+ id path line originalLine isResolved isOutdated
11
+ comments(first: 100) {
12
+ nodes { databaseId author { login } body bodyText url path diffHunk createdAt }
13
+ }
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
19
+ `;
20
+ const RESOLVE_THREAD_MUTATION = `
21
+ mutation($threadId: ID!) {
22
+ resolveReviewThread(input: { threadId: $threadId }) {
23
+ thread { isResolved }
24
+ }
25
+ }
26
+ `;
27
+ const UNRESOLVE_THREAD_MUTATION = `
28
+ mutation($threadId: ID!) {
29
+ unresolveReviewThread(input: { threadId: $threadId }) {
30
+ thread { isResolved }
31
+ }
32
+ }
33
+ `;
34
+ const authorLogin = (author) => author?.login ?? "ghost";
35
+ const mapConversationComment = (node) => ({
36
+ author: authorLogin(node.author),
37
+ body: node.body.trim(),
38
+ });
39
+ const mapThreadComment = (node) => ({
40
+ author: authorLogin(node.author),
41
+ body: node.body.trim(),
42
+ databaseId: node.databaseId,
43
+ url: node.url,
44
+ path: node.path,
45
+ diffHunk: node.diffHunk,
46
+ createdAt: node.createdAt,
47
+ });
48
+ const mapReviewThread = (node) => ({
49
+ id: node.id,
50
+ path: node.path,
51
+ line: node.line,
52
+ originalLine: node.originalLine,
53
+ isResolved: node.isResolved,
54
+ isOutdated: node.isOutdated,
55
+ comments: node.comments.nodes.map(mapThreadComment),
56
+ });
57
+ export const fetchPrComments = async (owner, name, number) => {
58
+ const data = await ghGraphql(FETCH_PR_COMMENTS_QUERY, {
59
+ owner,
60
+ name,
61
+ number,
62
+ });
63
+ const pullRequest = data.repository?.pullRequest;
64
+ if (!pullRequest) {
65
+ throw new Error(`pull request not found: ${owner}/${name}#${number}`);
66
+ }
67
+ return {
68
+ headRef: pullRequest.headRefName ?? "HEAD",
69
+ conversation: pullRequest.comments.nodes.map(mapConversationComment),
70
+ threads: pullRequest.reviewThreads.nodes.map(mapReviewThread),
71
+ };
72
+ };
73
+ export const replyToThread = async (options) => {
74
+ await ghRest(`repos/${options.repo}/pulls/${options.pull}/comments`, {
75
+ fields: { body: options.body, in_reply_to: options.inReplyTo },
76
+ });
77
+ };
78
+ export const resolveThread = async (threadId) => {
79
+ const data = await ghGraphql(RESOLVE_THREAD_MUTATION, {
80
+ threadId,
81
+ });
82
+ return data.resolveReviewThread.thread.isResolved;
83
+ };
84
+ export const unresolveThread = async (threadId) => {
85
+ const data = await ghGraphql(UNRESOLVE_THREAD_MUTATION, { threadId });
86
+ return data.unresolveReviewThread.thread.isResolved;
87
+ };
88
+ //# sourceMappingURL=pr-comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-comments.js","sourceRoot":"","sources":["../src/pr-comments.ts"],"names":[],"mappings":"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"}
package/dist/pr.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type CurrentPr = {
2
+ number: number;
3
+ repo: string;
4
+ url: string;
5
+ title: string;
6
+ branch: string;
7
+ };
8
+ export declare const resolveCurrentPr: () => Promise<CurrentPr>;
9
+ export declare const resolveCurrentRepo: () => Promise<string>;
10
+ //# sourceMappingURL=pr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr.d.ts","sourceRoot":"","sources":["../src/pr.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAKD,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,SAAS,CAyB1D,CAAA;AAGD,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,MAAM,CAUzD,CAAA"}
package/dist/pr.js ADDED
@@ -0,0 +1,35 @@
1
+ import { execa } from "execa";
2
+ // Resolve the open PR for the branch checked out in the current directory. The
3
+ // repo is taken from the PR url (canonical owner/name, robust to forks), falling
4
+ // back to the head repository fields.
5
+ export const resolveCurrentPr = async () => {
6
+ const { stdout } = await execa("gh", [
7
+ "pr",
8
+ "view",
9
+ "--json",
10
+ "number,title,url,headRefName,headRepository,headRepositoryOwner",
11
+ ]);
12
+ const pr = JSON.parse(stdout);
13
+ const repo = pr.url.match(/github\.com\/([^/]+\/[^/]+)\/pull\//)?.[1] ??
14
+ `${pr.headRepositoryOwner.login}/${pr.headRepository.name}`;
15
+ return {
16
+ number: pr.number,
17
+ repo,
18
+ url: pr.url,
19
+ title: pr.title,
20
+ branch: pr.headRefName,
21
+ };
22
+ };
23
+ // The `owner/name` of the repo in the current directory.
24
+ export const resolveCurrentRepo = async () => {
25
+ const { stdout } = await execa("gh", [
26
+ "repo",
27
+ "view",
28
+ "--json",
29
+ "nameWithOwner",
30
+ "-q",
31
+ ".nameWithOwner",
32
+ ]);
33
+ return stdout.trim();
34
+ };
35
+ //# sourceMappingURL=pr.js.map
package/dist/pr.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr.js","sourceRoot":"","sources":["../src/pr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAU7B,+EAA+E;AAC/E,iFAAiF;AACjF,sCAAsC;AACtC,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,IAAwB,EAAE;IAC7D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE;QACnC,IAAI;QACJ,MAAM;QACN,QAAQ;QACR,iEAAiE;KAClE,CAAC,CAAA;IACF,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAO3B,CAAA;IACD,MAAM,IAAI,GACR,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,qCAAqC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxD,GAAG,EAAE,CAAC,mBAAmB,CAAC,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;IAC7D,OAAO;QACL,MAAM,EAAE,EAAE,CAAC,MAAM;QACjB,IAAI;QACJ,GAAG,EAAE,EAAE,CAAC,GAAG;QACX,KAAK,EAAE,EAAE,CAAC,KAAK;QACf,MAAM,EAAE,EAAE,CAAC,WAAW;KACvB,CAAA;AACH,CAAC,CAAA;AAED,yDAAyD;AACzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,IAAqB,EAAE;IAC5D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE;QACnC,MAAM;QACN,MAAM;QACN,QAAQ;QACR,eAAe;QACf,IAAI;QACJ,gBAAgB;KACjB,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAA;AACtB,CAAC,CAAA"}
@@ -0,0 +1,23 @@
1
+ export type Hook = {
2
+ id: number;
3
+ name: string;
4
+ config: {
5
+ url: string;
6
+ };
7
+ events: string[];
8
+ active: boolean;
9
+ };
10
+ export declare const parsePrDeliveryId: (raw: string) => string | null;
11
+ export declare const fetchHooks: (repo: string) => Promise<Hook[]>;
12
+ export declare const fetchLatestPrDeliveryId: (repo: string, hookId: number) => Promise<string>;
13
+ export declare const replayDelivery: (repo: string, hookId: number, deliveryId: string) => Promise<void>;
14
+ export declare const findHook: (hooks: Hook[], pattern: string) => Hook | undefined;
15
+ export declare const findJenkinsHook: (hooks: Hook[]) => Hook | undefined;
16
+ export declare const fetchHookContexts: (repo: string, hookId: number) => Promise<string[]>;
17
+ export type ReplayResult = {
18
+ hook: Hook;
19
+ deliveryId: string;
20
+ };
21
+ export declare const replayLatestPrDelivery: (repo: string, pattern: string) => Promise<ReplayResult>;
22
+ export declare const retriggerJenkinsWebhook: (repo: string) => Promise<ReplayResult>;
23
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,IAAI,GAAG;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAOD,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAIxD,CAAA;AAED,eAAO,MAAM,UAAU,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,EAAE,CACgB,CAAA;AAE9E,eAAO,MAAM,uBAAuB,GAClC,MAAM,MAAM,EACZ,QAAQ,MAAM,KACb,OAAO,CAAC,MAAM,CAQhB,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,YAAY,MAAM,KACjB,OAAO,CAAC,IAAI,CAKd,CAAA;AAGD,eAAO,MAAM,QAAQ,GAAI,OAAO,IAAI,EAAE,EAAE,SAAS,MAAM,KAAG,IAAI,GAAG,SACc,CAAA;AAG/E,eAAO,MAAM,eAAe,GAAI,OAAO,IAAI,EAAE,KAAG,IAAI,GAAG,SAIpD,CAAA;AAKH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,QAAQ,MAAM,KACb,OAAO,CAAC,MAAM,EAAE,CA0BlB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAA;AAI7D,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,SAAS,MAAM,KACd,OAAO,CAAC,YAAY,CAOtB,CAAA;AAID,eAAO,MAAM,uBAAuB,GAClC,MAAM,MAAM,KACX,OAAO,CAAC,YAAY,CAOtB,CAAA"}
@@ -0,0 +1,71 @@
1
+ import { ghRest } from "./gh.js";
2
+ // The id of the most recent `pull_request` delivery in a hook's deliveries
3
+ // listing, or null if none. The listing is newest-first, so the first match is
4
+ // the latest. Parses the JSON array — the old shell tools text-scanned for
5
+ // `"pull_request"` and walked back to a nearby `"id"`, which silently depended
6
+ // on pretty-printed output; parsing is equivalent and robust.
7
+ export const parsePrDeliveryId = (raw) => {
8
+ const deliveries = JSON.parse(raw);
9
+ const pr = deliveries.find((d) => d.event === "pull_request");
10
+ return pr ? String(pr.id) : null;
11
+ };
12
+ export const fetchHooks = async (repo) => JSON.parse(await ghRest(`repos/${repo}/hooks`, { method: "GET" }));
13
+ export const fetchLatestPrDeliveryId = async (repo, hookId) => {
14
+ const raw = await ghRest(`repos/${repo}/hooks/${hookId}/deliveries?per_page=20`, { method: "GET" });
15
+ const id = parsePrDeliveryId(raw);
16
+ if (!id)
17
+ throw new Error(`No pull_request delivery found for hook ${hookId}`);
18
+ return id;
19
+ };
20
+ export const replayDelivery = async (repo, hookId, deliveryId) => {
21
+ await ghRest(`repos/${repo}/hooks/${hookId}/deliveries/${deliveryId}/attempts`, { method: "POST" });
22
+ };
23
+ // Match a hook by url substring or exact numeric id (the CLI `--hook` pattern).
24
+ export const findHook = (hooks, pattern) => hooks.find((h) => h.config.url.includes(pattern) || String(h.id) === pattern);
25
+ // The Jenkins PR-builder hook, by its well-known url markers.
26
+ export const findJenkinsHook = (hooks) => hooks.find((h) => h.config.url.includes("ghprbhook") || h.config.url.includes("jenkins"));
27
+ // The status contexts a hook's latest pull_request delivery produced — derived
28
+ // from the delivered head sha's commit statuses. Best-effort: returns [] rather
29
+ // than throwing so a picker can still list the hook.
30
+ export const fetchHookContexts = async (repo, hookId) => {
31
+ try {
32
+ const listing = await ghRest(`repos/${repo}/hooks/${hookId}/deliveries?per_page=5`, { method: "GET" });
33
+ const deliveryId = parsePrDeliveryId(listing);
34
+ if (!deliveryId)
35
+ return [];
36
+ const delivery = JSON.parse(await ghRest(`repos/${repo}/hooks/${hookId}/deliveries/${deliveryId}`, {
37
+ method: "GET",
38
+ }));
39
+ const sha = delivery?.request?.payload?.pull_request?.head?.sha;
40
+ if (!sha)
41
+ return [];
42
+ const statuses = JSON.parse(await ghRest(`repos/${repo}/commits/${sha}/statuses`, { method: "GET" }));
43
+ return [...new Set(statuses.map((s) => s.context))];
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ };
49
+ // Replay the latest pull_request delivery on the hook matching `pattern`
50
+ // (gh-webhook-replay command mode).
51
+ export const replayLatestPrDelivery = async (repo, pattern) => {
52
+ const hooks = await fetchHooks(repo);
53
+ const hook = findHook(hooks, pattern);
54
+ if (!hook)
55
+ throw new Error(`No webhook matching "${pattern}" on ${repo}`);
56
+ const deliveryId = await fetchLatestPrDeliveryId(repo, hook.id);
57
+ await replayDelivery(repo, hook.id, deliveryId);
58
+ return { hook, deliveryId };
59
+ };
60
+ // Re-fire the Jenkins PR-builder hook's latest pull_request delivery
61
+ // (gh-pr-health --retrigger).
62
+ export const retriggerJenkinsWebhook = async (repo) => {
63
+ const hooks = await fetchHooks(repo);
64
+ const hook = findJenkinsHook(hooks);
65
+ if (!hook)
66
+ throw new Error("No Jenkins webhook found");
67
+ const deliveryId = await fetchLatestPrDeliveryId(repo, hook.id);
68
+ await replayDelivery(repo, hook.id, deliveryId);
69
+ return { hook, deliveryId };
70
+ };
71
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAUhC,2EAA2E;AAC3E,+EAA+E;AAC/E,2EAA2E;AAC3E,+EAA+E;AAC/E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAAiB,EAAE;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyC,CAAA;IAC1E,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,cAAc,CAAC,CAAA;IAC7D,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAClC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAmB,EAAE,CAChE,IAAI,CAAC,KAAK,CAAC,MAAM,MAAM,CAAC,SAAS,IAAI,QAAQ,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAW,CAAA;AAE9E,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,EAC1C,IAAY,EACZ,MAAc,EACG,EAAE;IACnB,MAAM,GAAG,GAAG,MAAM,MAAM,CACtB,SAAS,IAAI,UAAU,MAAM,yBAAyB,EACtD,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAA;IACD,MAAM,EAAE,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,MAAM,EAAE,CAAC,CAAA;IAC7E,OAAO,EAAE,CAAA;AACX,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,IAAY,EACZ,MAAc,EACd,UAAkB,EACH,EAAE;IACjB,MAAM,MAAM,CACV,SAAS,IAAI,UAAU,MAAM,eAAe,UAAU,WAAW,EACjE,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAA;AACH,CAAC,CAAA;AAED,gFAAgF;AAChF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,OAAe,EAAoB,EAAE,CAC3E,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAA;AAE/E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAAoB,EAAE,CACjE,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CACzE,CAAA;AAEH,+EAA+E;AAC/E,gFAAgF;AAChF,qDAAqD;AACrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,IAAY,EACZ,MAAc,EACK,EAAE;IACrB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAC1B,SAAS,IAAI,UAAU,MAAM,wBAAwB,EACrD,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAA;QACD,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;QAC7C,IAAI,CAAC,UAAU;YAAE,OAAO,EAAE,CAAA;QAE1B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,MAAM,MAAM,CAAC,SAAS,IAAI,UAAU,MAAM,eAAe,UAAU,EAAE,EAAE;YACrE,MAAM,EAAE,KAAK;SACd,CAAC,CACH,CAAA;QACD,MAAM,GAAG,GACP,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,CAAA;QACrD,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAA;QAEnB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,MAAM,MAAM,CAAC,SAAS,IAAI,YAAY,GAAG,WAAW,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAC3C,CAAA;QAE/B,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC,CAAA;AAID,yEAAyE;AACzE,oCAAoC;AACpC,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EACzC,IAAY,EACZ,OAAe,EACQ,EAAE;IACzB,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACrC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,QAAQ,IAAI,EAAE,CAAC,CAAA;IACzE,MAAM,UAAU,GAAG,MAAM,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IAC/D,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAC/C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;AAC7B,CAAC,CAAA;AAED,qEAAqE;AACrE,8BAA8B;AAC9B,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,EAC1C,IAAY,EACW,EAAE;IACzB,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IACnC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IACtD,MAAM,UAAU,GAAG,MAAM,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;IAC/D,MAAM,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAC/C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;AAC7B,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@kud/gh",
3
+ "version": "0.1.0",
4
+ "description": "Surface-agnostic GitHub core — gh CLI primitives, PR review comments, health, and webhook logic.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p .",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "github",
26
+ "pull-request",
27
+ "code-review",
28
+ "gh-cli",
29
+ "graphql",
30
+ "core"
31
+ ],
32
+ "author": "Erwann Mest <m@kud.io>",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/kud/gh",
37
+ "directory": "packages/gh"
38
+ },
39
+ "engines": {
40
+ "node": ">=22"
41
+ },
42
+ "sideEffects": false,
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "execa": "9.6.1"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "26.1.1",
51
+ "typescript": "5.9.3",
52
+ "vitest": "4.1.10"
53
+ }
54
+ }