@kud/gh-workflow 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/dist/config.d.ts +82 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +49 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +263 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +301 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/map.d.ts +6 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/map.js +141 -0
- package/dist/map.js.map +1 -0
- package/package.json +52 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A named slice of the reader's repos. Two or more turn on the in-app toggle;
|
|
3
|
+
* one or none means an undivided set and no toggle at all.
|
|
4
|
+
*
|
|
5
|
+
* Named rather than boolean because the distinction was never a property of a
|
|
6
|
+
* repo — it is whatever separation the reader actually keeps, and a boolean
|
|
7
|
+
* leaves a third slice nowhere to go.
|
|
8
|
+
*/
|
|
9
|
+
export type RepoProfile = {
|
|
10
|
+
/** Stable id — what `--profile=<name>` selects. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Shown on the toggle. Defaults to `name`. */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** Which repos belong to this profile. */
|
|
15
|
+
match: (repo: string) => boolean;
|
|
16
|
+
/** Where this profile's checkouts live, if it keeps its own directory. */
|
|
17
|
+
checkoutDir?: string;
|
|
18
|
+
};
|
|
19
|
+
export type InboxConfig = {
|
|
20
|
+
/**
|
|
21
|
+
* Repo ranking, best first. An entry ending in `/` matches by owner prefix,
|
|
22
|
+
* anything else must equal `owner/name` exactly — so a single repo can outrank
|
|
23
|
+
* the owner that contains it. Repos matching nothing sort last, together.
|
|
24
|
+
*/
|
|
25
|
+
repoPriority: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Label ranking, best first, deciding which two labels a row shows when it
|
|
28
|
+
* carries more than two. An entry ending in `*` matches by prefix; anything
|
|
29
|
+
* else must equal the label name exactly. Labels matching nothing share the
|
|
30
|
+
* last rank and are ordered among themselves by name.
|
|
31
|
+
*
|
|
32
|
+
* Empty like everything else here, and that is load-bearing rather than
|
|
33
|
+
* merely consistent: `plan`, `spike` and `app:*` are one reader's filing
|
|
34
|
+
* conventions, invented in their own notes, and a library that shipped them
|
|
35
|
+
* as defaults would be ranking a stranger's labels by a scheme they have
|
|
36
|
+
* never seen. Empty means a labelled row shows its labels in name order,
|
|
37
|
+
* which is honest about knowing nothing.
|
|
38
|
+
*/
|
|
39
|
+
labelPriority: readonly string[];
|
|
40
|
+
profiles: readonly RepoProfile[];
|
|
41
|
+
/**
|
|
42
|
+
* Fallback directory holding checkouts, for repos in no profile that names one
|
|
43
|
+
* of its own. Unset means local checkouts are not resolved at all, which is
|
|
44
|
+
* correct for a host that only ever opens things in a browser.
|
|
45
|
+
*/
|
|
46
|
+
checkoutDir?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Directory name the on-disk cache lives under, inside the platform cache
|
|
49
|
+
* root. Defaults to this package's name — a library has no business claiming a
|
|
50
|
+
* directory named after whichever tool happens to embed it, and two hosts on
|
|
51
|
+
* one machine would otherwise share a cache keyed only by section name.
|
|
52
|
+
*/
|
|
53
|
+
cacheNamespace: string;
|
|
54
|
+
/**
|
|
55
|
+
* How long a cached inbox is trusted before a launch refetches. Every launch
|
|
56
|
+
* past it pays the full query — eight searches with check rollups — so this is
|
|
57
|
+
* the single knob between "always current" and "always slow, and drawing 502s
|
|
58
|
+
* from the API".
|
|
59
|
+
*
|
|
60
|
+
* It can be generous because staleness is bounded from the other end: acting
|
|
61
|
+
* on a row drops the entry outright, and `r` refetches on demand. What the TTL
|
|
62
|
+
* actually governs is how long a change made ELSEWHERE — someone approving
|
|
63
|
+
* your PR while you were not looking — can go unseen, which is a glance
|
|
64
|
+
* arriving late, not a wrong action taken.
|
|
65
|
+
*/
|
|
66
|
+
cacheTtlMs: number;
|
|
67
|
+
};
|
|
68
|
+
/** Supply the host's opinions. Call once, before rendering. */
|
|
69
|
+
export declare const configureInbox: (config: Partial<InboxConfig>) => void;
|
|
70
|
+
export declare const inboxConfig: () => InboxConfig;
|
|
71
|
+
/** Test seam — restores the empty defaults. */
|
|
72
|
+
export declare const resetInboxConfig: () => void;
|
|
73
|
+
/** The profile a repo belongs to, or null when none claims it. */
|
|
74
|
+
export declare const profileOf: (repo: string) => RepoProfile | null;
|
|
75
|
+
/**
|
|
76
|
+
* Where checkouts are searched, in order: every profile that names its own
|
|
77
|
+
* directory, then the fallback. Deduped, since the ordinary single-directory
|
|
78
|
+
* case points them all at the same place. Empty when nothing is configured —
|
|
79
|
+
* callers treat that as "not checked out locally", which it truthfully is.
|
|
80
|
+
*/
|
|
81
|
+
export declare const checkoutDirs: () => string[];
|
|
82
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAkBA;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAChC,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB;;;;OAIG;IACH,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAA;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;;OAWG;IACH,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAYD,+DAA+D;AAC/D,eAAO,MAAM,cAAc,GAAI,QAAQ,OAAO,CAAC,WAAW,CAAC,KAAG,IAE7D,CAAA;AAED,eAAO,MAAM,WAAW,QAAO,WAAsB,CAAA;AAErD,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,QAAO,IAEnC,CAAA;AAED,kEAAkE;AAClE,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,WAAW,GAAG,IACF,CAAA;AAErD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAO,MAAM,EAKrC,CAAA"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Host-supplied opinions.
|
|
2
|
+
//
|
|
3
|
+
// Everything here was once a constant compiled into the inbox: which repos to
|
|
4
|
+
// rank first, which ones counted as a separate part of the reader's life, where
|
|
5
|
+
// checkouts were kept on disk. None of that is a property of GitHub, and a
|
|
6
|
+
// library published to a registry cannot hold any of it — a ranking baked in at
|
|
7
|
+
// build time ranks one reader's repos above its next reader's.
|
|
8
|
+
//
|
|
9
|
+
// Set once at startup, before the first render, because it is process-global by
|
|
10
|
+
// nature: one reader, one machine, one arrangement of checkouts. Threading it
|
|
11
|
+
// through layoutGHItems → sortItems → repoPriority, and again through every
|
|
12
|
+
// filter that re-lays a section, would put a parameter on nine functions to
|
|
13
|
+
// carry a value that cannot vary within a run.
|
|
14
|
+
//
|
|
15
|
+
// Every default is empty. A host that configures nothing gets flat repo
|
|
16
|
+
// ordering, no profile split and no local-checkout resolution — never a guess at
|
|
17
|
+
// where someone keeps their code.
|
|
18
|
+
const EMPTY = {
|
|
19
|
+
repoPriority: [],
|
|
20
|
+
labelPriority: [],
|
|
21
|
+
profiles: [],
|
|
22
|
+
cacheNamespace: "gh-ink",
|
|
23
|
+
cacheTtlMs: 600_000,
|
|
24
|
+
};
|
|
25
|
+
let current = EMPTY;
|
|
26
|
+
/** Supply the host's opinions. Call once, before rendering. */
|
|
27
|
+
export const configureInbox = (config) => {
|
|
28
|
+
current = { ...EMPTY, ...config };
|
|
29
|
+
};
|
|
30
|
+
export const inboxConfig = () => current;
|
|
31
|
+
/** Test seam — restores the empty defaults. */
|
|
32
|
+
export const resetInboxConfig = () => {
|
|
33
|
+
current = EMPTY;
|
|
34
|
+
};
|
|
35
|
+
/** The profile a repo belongs to, or null when none claims it. */
|
|
36
|
+
export const profileOf = (repo) => current.profiles.find((p) => p.match(repo)) ?? null;
|
|
37
|
+
/**
|
|
38
|
+
* Where checkouts are searched, in order: every profile that names its own
|
|
39
|
+
* directory, then the fallback. Deduped, since the ordinary single-directory
|
|
40
|
+
* case points them all at the same place. Empty when nothing is configured —
|
|
41
|
+
* callers treat that as "not checked out locally", which it truthfully is.
|
|
42
|
+
*/
|
|
43
|
+
export const checkoutDirs = () => [
|
|
44
|
+
...new Set([
|
|
45
|
+
...current.profiles.flatMap((p) => p.checkoutDir ?? []),
|
|
46
|
+
...(current.checkoutDir ? [current.checkoutDir] : []),
|
|
47
|
+
]),
|
|
48
|
+
];
|
|
49
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,2EAA2E;AAC3E,gFAAgF;AAChF,+DAA+D;AAC/D,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,+CAA+C;AAC/C,EAAE;AACF,wEAAwE;AACxE,iFAAiF;AACjF,kCAAkC;AAuElC,MAAM,KAAK,GAAgB;IACzB,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,EAAE;IACjB,QAAQ,EAAE,EAAE;IACZ,cAAc,EAAE,QAAQ;IACxB,UAAU,EAAE,OAAO;CACpB,CAAA;AAED,IAAI,OAAO,GAAgB,KAAK,CAAA;AAEhC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAA4B,EAAQ,EAAE;IACnE,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,MAAM,EAAE,CAAA;AACnC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,GAAgB,EAAE,CAAC,OAAO,CAAA;AAErD,+CAA+C;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACzC,OAAO,GAAG,KAAK,CAAA;AACjB,CAAC,CAAA;AAED,kEAAkE;AAClE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAsB,EAAE,CAC5D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAA;AAErD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAa,EAAE,CAAC;IAC1C,GAAG,IAAI,GAAG,CAAC;QACT,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC;QACvD,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;CACH,CAAA"}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type { Health } from "@kud/gh/health";
|
|
2
|
+
/** Mirrors `@kud/ink-ui`'s `PillVariant`, structurally, so this package needs
|
|
3
|
+
* no renderer dependency to say which fill a row's pill takes. */
|
|
4
|
+
export type PillVariant = "success" | "error" | "warning" | "info" | "accent" | "muted";
|
|
5
|
+
export type GHDetail = {
|
|
6
|
+
reviewDecision?: string;
|
|
7
|
+
mergeable?: string;
|
|
8
|
+
checksPass: number;
|
|
9
|
+
checksFail: number;
|
|
10
|
+
checksPending: number;
|
|
11
|
+
checksStale?: number;
|
|
12
|
+
threadsTotal: number;
|
|
13
|
+
lastCommitAt?: string;
|
|
14
|
+
lastEventAt?: string;
|
|
15
|
+
};
|
|
16
|
+
export type GHItem = {
|
|
17
|
+
kind: "pr" | "issue";
|
|
18
|
+
number: number;
|
|
19
|
+
title: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
url: string;
|
|
22
|
+
branch?: string;
|
|
23
|
+
health: Health;
|
|
24
|
+
author?: string;
|
|
25
|
+
age: string;
|
|
26
|
+
activityAge?: string;
|
|
27
|
+
ts: number;
|
|
28
|
+
unresolved: number;
|
|
29
|
+
conversation: number;
|
|
30
|
+
lastActor?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The item's labels, unordered and by name only — which is all GitHub gives
|
|
33
|
+
* without the timeline API. `labels(first: N)` orders by when the label was
|
|
34
|
+
* created IN THE REPO, never by when it was applied here, so "the newest two"
|
|
35
|
+
* is not derivable and the row ranks them against the host's `labelPriority`
|
|
36
|
+
* instead.
|
|
37
|
+
*
|
|
38
|
+
* Optional because a `minimal` fetch omits the selection entirely — these
|
|
39
|
+
* fields vanish rather than degrade — so undefined means "not asked for",
|
|
40
|
+
* which the row draws as nothing at all rather than as an unlabelled item.
|
|
41
|
+
*/
|
|
42
|
+
labels?: readonly string[];
|
|
43
|
+
detail?: GHDetail;
|
|
44
|
+
/**
|
|
45
|
+
* Where YOU stand on this row, when the host knows it per row rather than per
|
|
46
|
+
* tab. Set it and the whose-move band reads it instead of inferring from the
|
|
47
|
+
* section id — which is what lets one tab hold rows of mixed standing: a
|
|
48
|
+
* review asked of you and one you have already given differ only in whether
|
|
49
|
+
* the ball comes back, and that is a fact about the search a row came from,
|
|
50
|
+
* never about the PR. Left unset, the section id decides as before.
|
|
51
|
+
*/
|
|
52
|
+
standing?: Standing;
|
|
53
|
+
/**
|
|
54
|
+
* A turn the viewer has claimed BY HAND, outranking everything inferred.
|
|
55
|
+
*
|
|
56
|
+
* The inference below is good and still gets things wrong in one direction it
|
|
57
|
+
* cannot see: a row can be genuinely yours while every signal says otherwise —
|
|
58
|
+
* nothing red, no unresolved thread, nobody waiting on a word from you — and
|
|
59
|
+
* no amount of reading the PR harder will discover it. That knowledge lives
|
|
60
|
+
* with the viewer, so this is the field where they put it.
|
|
61
|
+
*
|
|
62
|
+
* It is a PIN, not a correction: it applies whether or not the row was already
|
|
63
|
+
* yours, which is why it does not pair with an opposite. The other direction —
|
|
64
|
+
* "no reply is owed for this particular comment" — is a fact about one event
|
|
65
|
+
* and belongs where the events are read, not here; a host expresses it by
|
|
66
|
+
* handing back a `lastActor` that no longer claims a turn.
|
|
67
|
+
*/
|
|
68
|
+
pinned?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* What this row STANDS FOR, when that differs from what it is. Absent means
|
|
71
|
+
* the row is a unit of work and is counted as one.
|
|
72
|
+
*
|
|
73
|
+
* `"container"` marks a row that exists to carry context rather than to be
|
|
74
|
+
* done: the initiative a story hangs under, drawn as a real row because it is
|
|
75
|
+
* still selectable, openable, and worth seeing until it closes — but not
|
|
76
|
+
* itself a thing on anyone's plate. Four stories under one container is four
|
|
77
|
+
* items, not five.
|
|
78
|
+
*
|
|
79
|
+
* Deliberately not `uncounted`. A host knows what a row IS; it should not have
|
|
80
|
+
* to know what the tab badge does with that, and a field named after one
|
|
81
|
+
* consumer starts lying the moment a second one reads it.
|
|
82
|
+
*
|
|
83
|
+
* Orthogonal to `depth`, which says where the row hangs rather than what it
|
|
84
|
+
* stands for. A container is a genuine depth-0 row with genuine depth-1
|
|
85
|
+
* children; spelling it as a depth would mean lying about the tree to fix a
|
|
86
|
+
* number, and every site that draws indentation reads that lie as truth.
|
|
87
|
+
*
|
|
88
|
+
* A union with one member rather than a boolean. `indent` was a boolean that
|
|
89
|
+
* turned out to need a scalar, and widening it cost a deprecation that is
|
|
90
|
+
* still in this file. Adding a second role here is additive; turning a boolean
|
|
91
|
+
* into a union is not.
|
|
92
|
+
*/
|
|
93
|
+
role?: "container";
|
|
94
|
+
/**
|
|
95
|
+
* How deep this row hangs in the tree: 0 top level, 1 a child, 2 a
|
|
96
|
+
* grandchild. Absent means 0.
|
|
97
|
+
*
|
|
98
|
+
* Always read it through `depthOf`, never off the row — `indent` is still a
|
|
99
|
+
* legal way to spell "depth 1", and that helper is the only place that knows
|
|
100
|
+
* both spellings. A site that reads `item.indent` directly prices a `depth: 2`
|
|
101
|
+
* row as top level, which is the bug this field exists to end rather than a
|
|
102
|
+
* new one to introduce.
|
|
103
|
+
*/
|
|
104
|
+
depth?: number;
|
|
105
|
+
/**
|
|
106
|
+
* @deprecated Legacy spelling of `depth: 1`. Write `depth`; this is kept so
|
|
107
|
+
* every existing producer still typechecks, and it is read in exactly one
|
|
108
|
+
* place (`depthOf`). Due to be removed at the next major.
|
|
109
|
+
*/
|
|
110
|
+
indent?: boolean;
|
|
111
|
+
};
|
|
112
|
+
export type TaskRow = {
|
|
113
|
+
kind: "task";
|
|
114
|
+
key: string;
|
|
115
|
+
summary: string;
|
|
116
|
+
url: string;
|
|
117
|
+
status: string;
|
|
118
|
+
age: string;
|
|
119
|
+
/** What this row stands for. See `GHItem.role`. */
|
|
120
|
+
role?: "container";
|
|
121
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
122
|
+
depth?: number;
|
|
123
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
124
|
+
indent?: boolean;
|
|
125
|
+
instanceKey?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Trailing annotation, rendered dim after the summary — a recurrence marker,
|
|
128
|
+
* a source hint, anything secondary to the title. Its own node rather than
|
|
129
|
+
* part of `summary` so it can be dimmed, and so its width is measured
|
|
130
|
+
* separately instead of being smuggled past the truncation maths.
|
|
131
|
+
*/
|
|
132
|
+
note?: string;
|
|
133
|
+
/**
|
|
134
|
+
* A category this row BELONGS to — `epic`, `blocked`, `spike` — drawn as a
|
|
135
|
+
* filled pill after the summary.
|
|
136
|
+
*
|
|
137
|
+
* Deliberately not a second spelling of `note`, which the epic marker used to
|
|
138
|
+
* borrow. The two want opposite weights: a pill says the word itself is the
|
|
139
|
+
* information, while `note` is for a reference the reader follows — a parent
|
|
140
|
+
* ticket key, a source. Filling a breadcrumb gives it a weight it has not
|
|
141
|
+
* earned, and a row can legitimately carry both (a story under someone else's
|
|
142
|
+
* epic shows its parent's key AND, one day, a status of its own).
|
|
143
|
+
*/
|
|
144
|
+
pill?: string;
|
|
145
|
+
/** Which fill the pill takes. See `@kud/ink-ui`'s `PillVariant`. */
|
|
146
|
+
pillVariant?: PillVariant;
|
|
147
|
+
/**
|
|
148
|
+
* The Jira issue key behind this row, when one exists. Its PRESENCE is what
|
|
149
|
+
* turns on the ticket affordances — ↵ opens a menu led by `jira issue view`,
|
|
150
|
+
* and `t` transitions the issue. A row without it is just a task: ↵ opens its
|
|
151
|
+
* URL and nothing here shells out to `jira`.
|
|
152
|
+
*
|
|
153
|
+
* Separate from `key` because the two are not the same thing. `key` is the
|
|
154
|
+
* left column, and a caller is free to put anything legible there: cockpit
|
|
155
|
+
* puts the ticket key, `life` puts a Todoist project name. Reading the drill
|
|
156
|
+
* off `key` is what made `life` run `jira issue view "Maison "` and
|
|
157
|
+
* label it "View ticket" on a surface that has never touched Jira.
|
|
158
|
+
*/
|
|
159
|
+
ticket?: string;
|
|
160
|
+
};
|
|
161
|
+
export type RepoHeader = {
|
|
162
|
+
kind: "repo-header";
|
|
163
|
+
repo: string;
|
|
164
|
+
age: string;
|
|
165
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
166
|
+
depth?: number;
|
|
167
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
168
|
+
indent?: boolean;
|
|
169
|
+
};
|
|
170
|
+
export type ShowMore = {
|
|
171
|
+
kind: "show-more";
|
|
172
|
+
hidden: GHItem[];
|
|
173
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
174
|
+
depth?: number;
|
|
175
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
176
|
+
indent?: boolean;
|
|
177
|
+
};
|
|
178
|
+
export type ShowLess = {
|
|
179
|
+
kind: "show-less";
|
|
180
|
+
toHide: GHItem[];
|
|
181
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
182
|
+
depth?: number;
|
|
183
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
184
|
+
indent?: boolean;
|
|
185
|
+
};
|
|
186
|
+
export type SubgroupHeader = {
|
|
187
|
+
kind: "subgroup-header";
|
|
188
|
+
label: string;
|
|
189
|
+
age: string;
|
|
190
|
+
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
191
|
+
depth?: number;
|
|
192
|
+
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
193
|
+
indent?: boolean;
|
|
194
|
+
};
|
|
195
|
+
export type AnyItem = GHItem | TaskRow | RepoHeader | SubgroupHeader | ShowMore | ShowLess;
|
|
196
|
+
/**
|
|
197
|
+
* The one place either spelling of depth is read. Every other site goes through
|
|
198
|
+
* this, so a row written as `depth: 2` cannot be priced as top level by a
|
|
199
|
+
* reader that only knew about the boolean.
|
|
200
|
+
*
|
|
201
|
+
* Clamped and floored rather than trusted: a bad producer degrades to a flatter
|
|
202
|
+
* list, where the old behaviour was a `NaN` column width that renders as a
|
|
203
|
+
* blank row and gives you nothing to diagnose from.
|
|
204
|
+
*/
|
|
205
|
+
export declare const depthOf: (item?: AnyItem) => number;
|
|
206
|
+
export type CiStatus = {
|
|
207
|
+
job: string;
|
|
208
|
+
buildNumber: number;
|
|
209
|
+
result: string;
|
|
210
|
+
building: boolean;
|
|
211
|
+
url: string;
|
|
212
|
+
age: string;
|
|
213
|
+
};
|
|
214
|
+
export type Section = {
|
|
215
|
+
id: string;
|
|
216
|
+
label: string;
|
|
217
|
+
items: AnyItem[];
|
|
218
|
+
};
|
|
219
|
+
export declare const relativeTime: (iso: string) => string;
|
|
220
|
+
export declare const repoPriority: (repo: string) => number;
|
|
221
|
+
export declare const labelPriority: (name: string) => number;
|
|
222
|
+
export declare const sortItems: (items: GHItem[]) => GHItem[];
|
|
223
|
+
export declare const sortByRecency: (items: GHItem[]) => GHItem[];
|
|
224
|
+
export declare const insertRepoHeaders: (items: GHItem[]) => AnyItem[];
|
|
225
|
+
export type Standing = "authored" | "queued" | "spoken";
|
|
226
|
+
export declare const whoseMove: (health: Health, sectionId: string, standing?: Standing, theySpokeLast?: boolean, pinned?: boolean) => "you" | "them";
|
|
227
|
+
export declare const PIN_MARK = "+";
|
|
228
|
+
export declare const layoutGHItems: (items: GHItem[], sectionId: string, login?: string) => AnyItem[];
|
|
229
|
+
/**
|
|
230
|
+
* A two-way repo split, when a host has one. `undefined` — the ordinary case —
|
|
231
|
+
* means no split at all: every row stands.
|
|
232
|
+
*
|
|
233
|
+
* The package deliberately does not NAME the sides. It hardcoded "work" and
|
|
234
|
+
* "home" until 2026-08-27: one reader's two lives compiled into a library
|
|
235
|
+
* anyone can install, printed in a header where no other host would recognise
|
|
236
|
+
* the word. The predicate, the side, and the word are all yours.
|
|
237
|
+
*
|
|
238
|
+
* Chosen once, by the host, before the first paint. There is no in-app toggle,
|
|
239
|
+
* because a key that flipped it could only ever put the inbox out of step with
|
|
240
|
+
* the scope the command was started in, with nothing on screen to explain the
|
|
241
|
+
* disagreement.
|
|
242
|
+
*/
|
|
243
|
+
export type OriginSplit = {
|
|
244
|
+
/** Rows whose repo this matches are one side; everything else is the other. */
|
|
245
|
+
match: (repo: string) => boolean;
|
|
246
|
+
/** Which side to show. */
|
|
247
|
+
show: "matched" | "rest";
|
|
248
|
+
/** What to call the visible side in the header. Omitted, nothing is shown. */
|
|
249
|
+
label?: string;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Keep only one side of a two-way repo split, leaving non-GH rows (task)
|
|
253
|
+
* untouched. Mirrors filterByRepos' gh/other split.
|
|
254
|
+
*
|
|
255
|
+
* `keep` is positional — "the ones that matched" or "the rest" — and never a
|
|
256
|
+
* name. This took the literal strings "work" and "home" until 2026-08-27, which
|
|
257
|
+
* is one reader's life compiled into a published library: no other host has
|
|
258
|
+
* those two categories, and several have none at all.
|
|
259
|
+
*/
|
|
260
|
+
export declare const filterByOrigin: (sections: Section[], keep: "matched" | "rest", match: (repo: string) => boolean) => Section[];
|
|
261
|
+
export declare const filterBySearch: (sections: Section[], query: string) => Section[];
|
|
262
|
+
export declare const filterByRepos: (sections: Section[], repos: Set<string>) => Section[];
|
|
263
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAG5C;kEACkE;AAClE,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,OAAO,GACP,SAAS,GACT,MAAM,GACN,QAAQ,GACR,OAAO,CAAA;AAEX,MAAM,MAAM,QAAQ,GAAG;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IAKrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,IAAI,GAAG,OAAO,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,GAAG,EAAE,MAAM,CAAA;IAMX,WAAW,CAAC,EAAE,MAAM,CAAA;IAGpB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAGlB,YAAY,EAAE,MAAM,CAAA;IAGpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,QAAQ,CAAA;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,mDAAmD;IACnD,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,iBAAiB,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,OAAO,GACjB,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEtE;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,KAAG,MAIxC,CAAA;AAMD,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,OAAO,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,OAAO,EAAE,CAAA;CACjB,CAAA;AAOD,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAM1C,CAAA;AAMD,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAM3C,CAAA;AAYD,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAM5C,CAAA;AAmBD,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,EAQ9C,CAAA;AAKJ,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,EACd,CAAA;AAExC,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,EAAE,KAAG,OAAO,EAgB1D,CAAA;AAiBD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAwDvD,eAAO,MAAM,SAAS,GACpB,QAAQ,MAAM,EACd,WAAW,MAAM,EACjB,WAAW,QAAQ,EACnB,gBAAgB,OAAO,EACvB,SAAS,OAAO,KACf,KAAK,GAAG,MAqCV,CAAA;AAID,eAAO,MAAM,QAAQ,MAAM,CAAA;AAiB3B,eAAO,MAAM,aAAa,GACxB,OAAO,MAAM,EAAE,EACf,WAAW,MAAM,EAIjB,QAAQ,MAAM,KACb,OAAO,EA6BT,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,+EAA+E;IAC/E,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAChC,0BAA0B;IAC1B,IAAI,EAAE,SAAS,GAAG,MAAM,CAAA;IACxB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,OAAO,EAAE,EACnB,MAAM,SAAS,GAAG,MAAM,EACxB,OAAO,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAC/B,OAAO,EAyBL,CAAA;AASL,eAAO,MAAM,cAAc,GACzB,UAAU,OAAO,EAAE,EACnB,OAAO,MAAM,KACZ,OAAO,EAwBT,CAAA;AAED,eAAO,MAAM,aAAa,GACxB,UAAU,OAAO,EAAE,EACnB,OAAO,GAAG,CAAC,MAAM,CAAC,KACjB,OAAO,EAuBT,CAAA"}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// Workflow semantics for a GitHub inbox: what a row is, whose move it is, how
|
|
2
|
+
// rows sort, group and filter. Lifted verbatim out of @kud/gh-ink so a browser
|
|
3
|
+
// surface can reach it — the logic was never terminal-specific, only its
|
|
4
|
+
// address was.
|
|
5
|
+
import { inboxConfig } from "./config.js";
|
|
6
|
+
/**
|
|
7
|
+
* The one place either spelling of depth is read. Every other site goes through
|
|
8
|
+
* this, so a row written as `depth: 2` cannot be priced as top level by a
|
|
9
|
+
* reader that only knew about the boolean.
|
|
10
|
+
*
|
|
11
|
+
* Clamped and floored rather than trusted: a bad producer degrades to a flatter
|
|
12
|
+
* list, where the old behaviour was a `NaN` column width that renders as a
|
|
13
|
+
* blank row and gives you nothing to diagnose from.
|
|
14
|
+
*/
|
|
15
|
+
export const depthOf = (item) => {
|
|
16
|
+
if (!item)
|
|
17
|
+
return 0;
|
|
18
|
+
if (typeof item.depth === "number")
|
|
19
|
+
return Math.max(0, Math.floor(item.depth));
|
|
20
|
+
return item.indent === true ? 1 : 0;
|
|
21
|
+
};
|
|
22
|
+
// Everything a host needs to render a drilled-into row. `kind` is narrowed so a
|
|
23
|
+
// host can branch without re-testing item.kind.
|
|
24
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
25
|
+
export const relativeTime = (iso) => {
|
|
26
|
+
const diff = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
27
|
+
if (diff < 3600)
|
|
28
|
+
return `${Math.floor(diff / 60)}m`;
|
|
29
|
+
if (diff < 86400)
|
|
30
|
+
return `${Math.floor(diff / 3600)}h`;
|
|
31
|
+
if (diff < 604800)
|
|
32
|
+
return `${Math.floor(diff / 86400)}d`;
|
|
33
|
+
return `${Math.floor(diff / 604800)}w`;
|
|
34
|
+
};
|
|
35
|
+
// Rank from the host's ordered list rather than a compiled-in one. An entry
|
|
36
|
+
// ending in `/` matches an owner; anything else must equal `owner/name`, so a
|
|
37
|
+
// single repo can be pinned above the owner containing it. Unmatched repos share
|
|
38
|
+
// the last rank, which leaves the name tiebreak in sortItems to order them.
|
|
39
|
+
export const repoPriority = (repo) => {
|
|
40
|
+
const order = inboxConfig().repoPriority;
|
|
41
|
+
const i = order.findIndex((p) => p.endsWith("/") ? repo.startsWith(p) : repo === p);
|
|
42
|
+
return i === -1 ? order.length : i;
|
|
43
|
+
};
|
|
44
|
+
// The same idea for labels, with two deliberate differences. An entry ending in
|
|
45
|
+
// `*` matches by prefix — `app:*` covers every per-app label without listing
|
|
46
|
+
// them — where the repo form uses a trailing `/`, because `/` is a real
|
|
47
|
+
// separator in a repo name and `*` is not a character a label may contain.
|
|
48
|
+
//
|
|
49
|
+
// And unmatched labels rank Infinity rather than `order.length`, so they all
|
|
50
|
+
// share one rank and the localeCompare tiebreak orders them among themselves.
|
|
51
|
+
// With `order.length` an unmatched label would tie with nothing below it and the
|
|
52
|
+
// distinction would be invisible — same behaviour today, but it stops being the
|
|
53
|
+
// same the moment a rank is compared against anything other than another rank.
|
|
54
|
+
export const labelPriority = (name) => {
|
|
55
|
+
const order = inboxConfig().labelPriority;
|
|
56
|
+
const i = order.findIndex((p) => p.endsWith("*") ? name.startsWith(p.slice(0, -1)) : name === p);
|
|
57
|
+
return i === -1 ? Infinity : i;
|
|
58
|
+
};
|
|
59
|
+
// Repo grouping is the OUTER key and is deliberately unchanged — priority tier,
|
|
60
|
+
// then repo name — because insertRepoHeaders below depends on same-repo items
|
|
61
|
+
// staying adjacent, and a strict recency sort scatters a repo down the list.
|
|
62
|
+
// Within a repo, two keys break the tie: draft-ness, then recency — the one
|
|
63
|
+
// place either can reorder rows without costing the grouping.
|
|
64
|
+
//
|
|
65
|
+
// Draft sinks because a draft is not asking. Every tab but `draft` itself is a
|
|
66
|
+
// RELATIONSHIP ("they requested you", "it's on your repo"), and a draft row
|
|
67
|
+
// answers the only question those tabs pose with "not yet" — including the case
|
|
68
|
+
// that reads worst, a PR that had you requested and was then converted BACK to
|
|
69
|
+
// draft. Recency alone floated exactly that row to the top of the list, since a
|
|
70
|
+
// fresh draft outranks an open PR someone has genuinely been waiting on for a
|
|
71
|
+
// fortnight. Sunk, not filtered: a draft you were deliberately asked to look at
|
|
72
|
+
// early must still be visible, and it keeps its `~` glyph either way.
|
|
73
|
+
//
|
|
74
|
+
// Sorting on `ts` and not on `age`: `age` is a rendered string ("23h", "2d") and
|
|
75
|
+
// sorts lexicographically, which puts "2d" before "23h".
|
|
76
|
+
export const sortItems = (items) => [...items].sort((a, b) => {
|
|
77
|
+
const pd = repoPriority(a.repo) - repoPriority(b.repo);
|
|
78
|
+
if (pd !== 0)
|
|
79
|
+
return pd;
|
|
80
|
+
const rd = a.repo.localeCompare(b.repo);
|
|
81
|
+
if (rd !== 0)
|
|
82
|
+
return rd;
|
|
83
|
+
const dd = Number(a.health === "draft") - Number(b.health === "draft");
|
|
84
|
+
return dd !== 0 ? dd : b.ts - a.ts;
|
|
85
|
+
});
|
|
86
|
+
// Flat, newest-first ordering. Repos are *not* clustered — an item's repo
|
|
87
|
+
// header still appears (via insertRepoHeaders), but only when the repo changes
|
|
88
|
+
// as we walk down the timeline, so the same repo can recur further down.
|
|
89
|
+
export const sortByRecency = (items) => [...items].sort((a, b) => b.ts - a.ts);
|
|
90
|
+
export const insertRepoHeaders = (items) => {
|
|
91
|
+
const result = [];
|
|
92
|
+
let lastRepo = "";
|
|
93
|
+
for (const item of items) {
|
|
94
|
+
if (depthOf(item) === 0 && item.repo !== lastRepo) {
|
|
95
|
+
lastRepo = item.repo;
|
|
96
|
+
result.push({
|
|
97
|
+
kind: "repo-header",
|
|
98
|
+
repo: item.repo,
|
|
99
|
+
age: "",
|
|
100
|
+
indent: false,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
result.push(item);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
// The per-TAB fallback, for a host whose every row in a tab shares a standing.
|
|
108
|
+
// A host that merges two searches into one tab sets `standing` on the rows
|
|
109
|
+
// instead, and this is never consulted for them.
|
|
110
|
+
const STANDING = {
|
|
111
|
+
mine: "authored",
|
|
112
|
+
// `open` and `draft` predate `mine` and are the same standing: a host that
|
|
113
|
+
// still splits its own PRs by draft-ness keeps working, and one that folds
|
|
114
|
+
// them into a single tab (as cockpit does — the band already sinks a draft,
|
|
115
|
+
// so the split said it twice) gets the same reading.
|
|
116
|
+
open: "authored",
|
|
117
|
+
draft: "authored",
|
|
118
|
+
assigned: "authored",
|
|
119
|
+
issues: "authored",
|
|
120
|
+
review: "queued",
|
|
121
|
+
incoming: "queued",
|
|
122
|
+
reviewed: "spoken",
|
|
123
|
+
};
|
|
124
|
+
// What is YOURS from each position. Read down a column and the flips are the
|
|
125
|
+
// point: the mechanical blockers (ci-fail, conflict, changes-req) are yours only
|
|
126
|
+
// on your own PR, and the queue states (waiting, pending) only while a review is
|
|
127
|
+
// still wanted from you. `threads` alone is yours from all three — it is
|
|
128
|
+
// literally "your reply is owed", and it is the only thing left on a PR you have
|
|
129
|
+
// already reviewed.
|
|
130
|
+
//
|
|
131
|
+
// `draft` is yours ONLY when you authored it, and that asymmetry is the whole
|
|
132
|
+
// point of listing it here. The band asks whose move it is, and on your own
|
|
133
|
+
// draft there is no one else in the room — nobody can advance it, nobody has
|
|
134
|
+
// been asked to, and filing it under Their move said the opposite of what was
|
|
135
|
+
// true. Somebody else's draft you were pointed at stays theirs, which is why
|
|
136
|
+
// this appears in `authored` and not in the other two.
|
|
137
|
+
//
|
|
138
|
+
// It is still SUNK to the bottom of its band by sortItems: yours to finish is
|
|
139
|
+
// not the same as yours to finish now, and a fresh draft must not outrank a PR
|
|
140
|
+
// somebody has genuinely been waiting on. Visible as yours, ranked last.
|
|
141
|
+
//
|
|
142
|
+
// `none` remains unlisted from every position — an issue has no review state to
|
|
143
|
+
// read, so claiming it would be a guess rather than a reading.
|
|
144
|
+
const YOURS = {
|
|
145
|
+
authored: [
|
|
146
|
+
"ci-fail",
|
|
147
|
+
"conflict",
|
|
148
|
+
"changes-req",
|
|
149
|
+
"threads",
|
|
150
|
+
"approved",
|
|
151
|
+
"draft",
|
|
152
|
+
],
|
|
153
|
+
queued: ["waiting", "pending", "threads", "approved"],
|
|
154
|
+
spoken: ["threads"],
|
|
155
|
+
};
|
|
156
|
+
// The row's own standing wins where the host set one; the tab decides otherwise,
|
|
157
|
+
// and an unrecognised tab reads as `queued` — over-claiming a stranger's PR as
|
|
158
|
+
// your work is the worse wrong guess.
|
|
159
|
+
export const whoseMove = (health, sectionId, standing, theySpokeLast, pinned) => {
|
|
160
|
+
const position = standing ?? STANDING[sectionId] ?? "queued";
|
|
161
|
+
// First, and unconditionally. Everything below is inference from what GitHub
|
|
162
|
+
// reports; this is the viewer having said so outright, and inference does not
|
|
163
|
+
// get to argue with it. It is one-directional by design — there is no way to
|
|
164
|
+
// pin a row AWAY, because "not mine" is what the bands already conclude on
|
|
165
|
+
// their own and a second control for it would only be a way to hide work.
|
|
166
|
+
if (pinned)
|
|
167
|
+
return "you";
|
|
168
|
+
// Somebody else having the last word is a claim on you — a question asked, an
|
|
169
|
+
// objection raised, a "can you rebase" — and none of it shows up as a health,
|
|
170
|
+
// because a bare comment approves nothing, fails nothing and opens no thread.
|
|
171
|
+
// The row already said so and the band disagreed: the turn arrow reads
|
|
172
|
+
// lastActor and drew `←`, the explain panel spelled out "X spoke last, your
|
|
173
|
+
// reply is owed", and the band filed it under Their move.
|
|
174
|
+
//
|
|
175
|
+
// ON YOUR OWN PR ONLY, though. Read unconditionally it destroys the very
|
|
176
|
+
// distinction the table below is built on — the mechanical blockers (ci-fail,
|
|
177
|
+
// conflict, changes-req) are yours on your PR and theirs on theirs — so a
|
|
178
|
+
// stranger's failing build became your move the moment they commented on it.
|
|
179
|
+
// Shipped that way for one release on 2026-08-27; eleven rows of somebody
|
|
180
|
+
// else's work turned up under Your move, which is exactly the noise the bands
|
|
181
|
+
// exist to prevent.
|
|
182
|
+
//
|
|
183
|
+
// The other two positions need no help from it: a review actually wanted from
|
|
184
|
+
// you is `waiting`/`pending`, and a conversation you are in is `threads`, both
|
|
185
|
+
// already listed. What is deliberately NOT claimed is a plain reply on a PR
|
|
186
|
+
// you reviewed once — real, but indistinguishable from the author saying
|
|
187
|
+
// "rebased" to nobody in particular.
|
|
188
|
+
//
|
|
189
|
+
// One direction only, even here: YOU having spoken last does not hand the row
|
|
190
|
+
// over, since red CI on your own PR is yours whether or not you commented
|
|
191
|
+
// after it.
|
|
192
|
+
if (theySpokeLast && position === "authored")
|
|
193
|
+
return "you";
|
|
194
|
+
return YOURS[position].includes(health) ? "you" : "them";
|
|
195
|
+
};
|
|
196
|
+
// Exported so the colourblind invariant can be tested against the health glyphs
|
|
197
|
+
// rather than restated as a literal in two files that drift apart.
|
|
198
|
+
export const PIN_MARK = "+";
|
|
199
|
+
const BAND_LABEL = {
|
|
200
|
+
you: "Your move",
|
|
201
|
+
them: "Their move",
|
|
202
|
+
};
|
|
203
|
+
// Lay out a section's GH items. The Done tab is a flat newest-first list; every
|
|
204
|
+
// other tab splits into two whose-move bands, each keeping its own repo grouping
|
|
205
|
+
// so the outer key stays legible inside a band. Repo headers are inserted in
|
|
206
|
+
// every case, and restart per band — a repo with work on both sides of the line
|
|
207
|
+
// appears under each.
|
|
208
|
+
//
|
|
209
|
+
// One band is not a band: a tab whose rows all land on the same side gains two
|
|
210
|
+
// header rows and no information, so it keeps the plain list. That is not an
|
|
211
|
+
// edge case, it is the Draft and Issues tabs by construction — every row there
|
|
212
|
+
// carries the same health token, so the split has nothing to say.
|
|
213
|
+
export const layoutGHItems = (items, sectionId,
|
|
214
|
+
// Needed to read `lastActor`, which is only meaningful against somebody. Left
|
|
215
|
+
// out, the bands fall back to health and standing alone — the behaviour every
|
|
216
|
+
// caller had before, so an un-updated host degrades rather than breaks.
|
|
217
|
+
login) => {
|
|
218
|
+
if (sectionId === "done")
|
|
219
|
+
return insertRepoHeaders(sortByRecency(items));
|
|
220
|
+
const sorted = sortItems(items);
|
|
221
|
+
const bands = ["you", "them"].map((side) => ({
|
|
222
|
+
side,
|
|
223
|
+
rows: sorted.filter((i) => whoseMove(i.health, sectionId, i.standing, !!login && !!i.lastActor && i.lastActor !== login, i.pinned) === side),
|
|
224
|
+
}));
|
|
225
|
+
const filled = bands.filter((b) => b.rows.length > 0);
|
|
226
|
+
if (filled.length < 2)
|
|
227
|
+
return insertRepoHeaders(sorted);
|
|
228
|
+
return filled.flatMap(({ side, rows }) => [
|
|
229
|
+
{
|
|
230
|
+
kind: "subgroup-header",
|
|
231
|
+
label: `${BAND_LABEL[side]} (${rows.length})`,
|
|
232
|
+
age: "",
|
|
233
|
+
indent: false,
|
|
234
|
+
},
|
|
235
|
+
...insertRepoHeaders(rows),
|
|
236
|
+
]);
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Keep only one side of a two-way repo split, leaving non-GH rows (task)
|
|
240
|
+
* untouched. Mirrors filterByRepos' gh/other split.
|
|
241
|
+
*
|
|
242
|
+
* `keep` is positional — "the ones that matched" or "the rest" — and never a
|
|
243
|
+
* name. This took the literal strings "work" and "home" until 2026-08-27, which
|
|
244
|
+
* is one reader's life compiled into a published library: no other host has
|
|
245
|
+
* those two categories, and several have none at all.
|
|
246
|
+
*/
|
|
247
|
+
export const filterByOrigin = (sections, keep, match) => sections
|
|
248
|
+
.map((s) => {
|
|
249
|
+
const kept = s.items.filter((i) => i.kind !== "repo-header" &&
|
|
250
|
+
i.kind !== "subgroup-header" &&
|
|
251
|
+
i.kind !== "show-more" &&
|
|
252
|
+
i.kind !== "show-less" &&
|
|
253
|
+
(i.kind === "pr" || i.kind === "issue"
|
|
254
|
+
? keep === "matched"
|
|
255
|
+
? match(i.repo)
|
|
256
|
+
: !match(i.repo)
|
|
257
|
+
: true));
|
|
258
|
+
const gh = kept.filter((i) => i.kind === "pr" || i.kind === "issue");
|
|
259
|
+
const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
|
|
260
|
+
return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
|
|
261
|
+
})
|
|
262
|
+
.filter((s) => s.items.some((i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"));
|
|
263
|
+
const searchText = (i) => i.kind === "pr" || i.kind === "issue"
|
|
264
|
+
? `${i.title} ${i.repo} #${i.number}`
|
|
265
|
+
: i.kind === "task"
|
|
266
|
+
? `${i.summary} ${i.key}`
|
|
267
|
+
: "";
|
|
268
|
+
export const filterBySearch = (sections, query) => {
|
|
269
|
+
const q = query.trim().toLowerCase();
|
|
270
|
+
if (!q)
|
|
271
|
+
return sections;
|
|
272
|
+
return sections
|
|
273
|
+
.map((s) => {
|
|
274
|
+
const kept = s.items.filter((i) => i.kind !== "repo-header" &&
|
|
275
|
+
i.kind !== "subgroup-header" &&
|
|
276
|
+
i.kind !== "show-more" &&
|
|
277
|
+
i.kind !== "show-less" &&
|
|
278
|
+
searchText(i).toLowerCase().includes(q));
|
|
279
|
+
const gh = kept.filter((i) => i.kind === "pr" || i.kind === "issue");
|
|
280
|
+
const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
|
|
281
|
+
return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
|
|
282
|
+
})
|
|
283
|
+
.filter((s) => s.items.some((i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"));
|
|
284
|
+
};
|
|
285
|
+
export const filterByRepos = (sections, repos) => {
|
|
286
|
+
if (repos.size === 0)
|
|
287
|
+
return sections;
|
|
288
|
+
return sections
|
|
289
|
+
.map((s) => {
|
|
290
|
+
const kept = s.items.filter((i) => i.kind !== "repo-header" &&
|
|
291
|
+
i.kind !== "subgroup-header" &&
|
|
292
|
+
i.kind !== "show-more" &&
|
|
293
|
+
i.kind !== "show-less" &&
|
|
294
|
+
(i.kind === "pr" || i.kind === "issue" ? repos.has(i.repo) : true));
|
|
295
|
+
const gh = kept.filter((i) => i.kind === "pr" || i.kind === "issue");
|
|
296
|
+
const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
|
|
297
|
+
return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
|
|
298
|
+
})
|
|
299
|
+
.filter((s) => s.items.some((i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"));
|
|
300
|
+
};
|
|
301
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @kud/gh-workflow — the workflow half of a GitHub inbox, with no surface
|
|
2
|
+
// attached. Everything here is a total function over plain data, so a terminal
|
|
3
|
+
// renderer, a browser and a server route all consume it alike.
|
|
4
|
+
//
|
|
5
|
+
// The hard rule this package exists to keep: nothing in here may import a
|
|
6
|
+
// renderer, a transport or the filesystem. `imports.test.ts` asserts it against
|
|
7
|
+
// the BUILT output rather than the source, because in-workspace every forbidden
|
|
8
|
+
// dependency is installed and an accidental import simply succeeds.
|
|
9
|
+
export * from "./core.js";
|
|
10
|
+
export * from "./map.js";
|
|
11
|
+
export * from "./config.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,+EAA+E;AAC/E,+DAA+D;AAC/D,EAAE;AACF,0EAA0E;AAC1E,gFAAgF;AAChF,gFAAgF;AAChF,oEAAoE;AACpE,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA"}
|
package/dist/map.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAWA,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AAwFpE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAA;AAEhC,eAAO,MAAM,QAAQ,GACnB,MAAM,GAAG,EACT,OAAM;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,KAC9B,WA0EF,CAAA"}
|
package/dist/map.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// GraphQL node → row. Sat in @kud/gh-cockpit next to signalPath(), which
|
|
2
|
+
// mkdirSync-es a cache directory — so importing the mapper touched the
|
|
3
|
+
// filesystem. It does not any more.
|
|
4
|
+
import { computeHealth, latestChecks, isPassCheck, isFailCheck, isPendingCheck, } from "@kud/gh/health";
|
|
5
|
+
import { relativeTime } from "./core.js";
|
|
6
|
+
const viewerReacted = (n, content) => (n?.reactionGroups ?? []).some((g) => g?.content === content && g?.viewerHasReacted);
|
|
7
|
+
const conversationOf = (node) => {
|
|
8
|
+
// `acked` is only ever set on the PR-level comments, because they are the only
|
|
9
|
+
// events @kud/gh fetches reactions for. Reviews and thread replies carry none
|
|
10
|
+
// and read as unacked, which is the safe direction: an ack that cannot be seen
|
|
11
|
+
// leaves the turn where it was rather than clearing one nobody claimed.
|
|
12
|
+
const events = [];
|
|
13
|
+
let count = node.comments?.totalCount ?? 0;
|
|
14
|
+
const isBot = (author) => author?.__typename === "Bot";
|
|
15
|
+
for (const c of node.comments?.nodes ?? [])
|
|
16
|
+
events.push({
|
|
17
|
+
at: c.createdAt,
|
|
18
|
+
login: c.author?.login,
|
|
19
|
+
bot: isBot(c.author),
|
|
20
|
+
acked: viewerReacted(c, "EYES"),
|
|
21
|
+
});
|
|
22
|
+
for (const r of node.reviews?.nodes ?? [])
|
|
23
|
+
if (r.state !== "PENDING")
|
|
24
|
+
events.push({
|
|
25
|
+
at: r.submittedAt,
|
|
26
|
+
login: r.author?.login,
|
|
27
|
+
bot: isBot(r.author),
|
|
28
|
+
});
|
|
29
|
+
for (const t of node.reviewThreads?.nodes ?? []) {
|
|
30
|
+
count += t.comments?.totalCount ?? 0;
|
|
31
|
+
for (const c of t.comments?.nodes ?? [])
|
|
32
|
+
events.push({
|
|
33
|
+
at: c.createdAt,
|
|
34
|
+
login: c.author?.login,
|
|
35
|
+
bot: isBot(c.author),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const last = events
|
|
39
|
+
.filter((e) => e.at)
|
|
40
|
+
.sort((a, b) => a.at.localeCompare(b.at))
|
|
41
|
+
.pop();
|
|
42
|
+
// Only the LAST event needs testing: a push later than it is later than every
|
|
43
|
+
// event before it too. ISO-8601 UTC compares lexicographically, the same trick
|
|
44
|
+
// the stall test in @kud/gh-ink already relies on.
|
|
45
|
+
const pushedAt = node.commits?.nodes?.[0]?.commit?.committedDate;
|
|
46
|
+
const answeredByPush = Boolean(last?.bot && pushedAt && pushedAt > last.at);
|
|
47
|
+
// An ack moves no clock: unlike a push it is not an event with a time of its
|
|
48
|
+
// own here, so the comment it settles stays the last thing that happened and
|
|
49
|
+
// the row keeps reading as active. Only the turn changes.
|
|
50
|
+
if (answeredByPush || last?.acked)
|
|
51
|
+
return {
|
|
52
|
+
count,
|
|
53
|
+
lastActor: node.author?.login,
|
|
54
|
+
lastEventAt: answeredByPush ? pushedAt : last?.at,
|
|
55
|
+
};
|
|
56
|
+
return { count, lastActor: last?.login, lastEventAt: last?.at };
|
|
57
|
+
};
|
|
58
|
+
const detailOf = (node, lastEventAt) => {
|
|
59
|
+
const active = latestChecks(node.statusCheckRollup?.contexts?.nodes ?? []);
|
|
60
|
+
return {
|
|
61
|
+
reviewDecision: node.reviewDecision ?? undefined,
|
|
62
|
+
mergeable: node.mergeable ?? undefined,
|
|
63
|
+
checksPass: active.filter(isPassCheck).length,
|
|
64
|
+
checksFail: active.filter(isFailCheck).length,
|
|
65
|
+
checksPending: active.filter(isPendingCheck).length,
|
|
66
|
+
threadsTotal: (node.reviewThreads?.nodes ?? []).length,
|
|
67
|
+
lastCommitAt: node.commits?.nodes?.[0]?.commit?.committedDate,
|
|
68
|
+
lastEventAt,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export const toGHItem = (node, opts = {}) => {
|
|
72
|
+
const completedAt = node.mergedAt ?? node.closedAt ?? node.createdAt;
|
|
73
|
+
const convo = conversationOf(node);
|
|
74
|
+
// What "last activity" means, and it is deliberately NOT GitHub's `updatedAt`.
|
|
75
|
+
// That field bumps on label and assignee churn, so a triage pass would float
|
|
76
|
+
// every issue it touched above a PR someone had actually replied to. These
|
|
77
|
+
// three are the events a human would call activity, and every one of them is
|
|
78
|
+
// already in the response — conversationOf folds the last comment, review and
|
|
79
|
+
// thread reply into lastEventAt, and the last commit rides along beside it. So
|
|
80
|
+
// this costs nothing: no extra field, no extra node, no extra GraphQL points.
|
|
81
|
+
//
|
|
82
|
+
// Compared as ISO-8601 UTC strings rather than parsed, the same lexicographic
|
|
83
|
+
// trick conversationOf above already relies on.
|
|
84
|
+
const activityAt = [
|
|
85
|
+
node.createdAt,
|
|
86
|
+
convo.lastEventAt,
|
|
87
|
+
node.commits?.nodes?.[0]?.commit?.committedDate,
|
|
88
|
+
]
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.sort()
|
|
91
|
+
.pop();
|
|
92
|
+
// Done rows sort and read by completion — that is their recency, and the
|
|
93
|
+
// recentlyDone fragment fetches no conversation to derive anything else from.
|
|
94
|
+
// Everywhere else the item is open, so `age` stays the opening date and
|
|
95
|
+
// activity is the new, separate value.
|
|
96
|
+
const isDone = Boolean(node.mergedAt ?? node.closedAt);
|
|
97
|
+
const sortAt = isDone ? completedAt : (activityAt ?? completedAt);
|
|
98
|
+
return {
|
|
99
|
+
// Prefer GraphQL's own discriminator; fall back to field-presence only when
|
|
100
|
+
// a fragment omitted __typename. Field-presence alone mislabels PRs as issues
|
|
101
|
+
// whenever a fragment (reviewRequests, assigned, reviewed…) skips isDraft /
|
|
102
|
+
// headRefName — which sent PR drills to the issues endpoint.
|
|
103
|
+
kind: node.__typename === "PullRequest"
|
|
104
|
+
? "pr"
|
|
105
|
+
: node.__typename === "Issue"
|
|
106
|
+
? "issue"
|
|
107
|
+
: "isDraft" in node || "headRefName" in node
|
|
108
|
+
? "pr"
|
|
109
|
+
: "issue",
|
|
110
|
+
number: node.number,
|
|
111
|
+
title: node.title ?? "",
|
|
112
|
+
repo: node.repository?.nameWithOwner ?? "",
|
|
113
|
+
url: node.url ?? "",
|
|
114
|
+
branch: node.headRefName,
|
|
115
|
+
health: computeHealth(node),
|
|
116
|
+
author: node.author?.login,
|
|
117
|
+
age: completedAt ? relativeTime(completedAt) : "",
|
|
118
|
+
activityAge: !isDone && activityAt ? relativeTime(activityAt) : undefined,
|
|
119
|
+
ts: sortAt ? new Date(sortAt).getTime() : 0,
|
|
120
|
+
unresolved: (node.reviewThreads?.nodes ?? []).filter((t) => t && !t.isResolved).length,
|
|
121
|
+
conversation: convo.count,
|
|
122
|
+
lastActor: convo.lastActor,
|
|
123
|
+
// 👍 on the PR BODY, and the anchor is load-bearing rather than incidental.
|
|
124
|
+
// Scoped to a comment this would quietly evaporate on the next bot comment,
|
|
125
|
+
// which is exactly backwards for something meant to persist — anchoring it
|
|
126
|
+
// on the PR makes it PR-scoped by construction, with no bookkeeping to get
|
|
127
|
+
// wrong. It also reads distinctly on GitHub itself: an eye down in the
|
|
128
|
+
// thread, a thumb at the top.
|
|
129
|
+
//
|
|
130
|
+
// Precedence over 👀 comes for free: the pin is read by whoseMove, which
|
|
131
|
+
// answers before anything derived from lastActor is consulted. Nobody pins
|
|
132
|
+
// by accident, so the pin wins.
|
|
133
|
+
pinned: viewerReacted(node, "THUMBS_UP"),
|
|
134
|
+
labels: (node.labels?.nodes ?? [])
|
|
135
|
+
.map((l) => l?.name)
|
|
136
|
+
.filter((n) => typeof n === "string"),
|
|
137
|
+
detail: detailOf(node, convo.lastEventAt),
|
|
138
|
+
indent: opts.indent ?? false,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=map.js.map
|
package/dist/map.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kud/gh-workflow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pure workflow semantics for a GitHub inbox — whose move it is, how rows sort, group and filter. No renderer, no transport, no filesystem.",
|
|
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": "npm run build && vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"github",
|
|
26
|
+
"inbox",
|
|
27
|
+
"workflow",
|
|
28
|
+
"pull-request",
|
|
29
|
+
"triage"
|
|
30
|
+
],
|
|
31
|
+
"author": "Erwann Mest <m@kud.io>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/kud/gh",
|
|
36
|
+
"directory": "packages/gh-workflow"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22"
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@kud/gh": "0.9.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"typescript": "5.9.3",
|
|
50
|
+
"vitest": "4.1.10"
|
|
51
|
+
}
|
|
52
|
+
}
|