@kud/gh-ink 0.19.0 → 0.21.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/index.d.ts +88 -1
- package/dist/index.js +57 -23
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -311,4 +311,91 @@ declare const writeCache: (key: string, data: {
|
|
|
311
311
|
login: string;
|
|
312
312
|
}) => void;
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
/**
|
|
315
|
+
* A named slice of the reader's repos. Two or more turn on the in-app toggle;
|
|
316
|
+
* one or none means an undivided set and no toggle at all.
|
|
317
|
+
*
|
|
318
|
+
* Named rather than boolean because the distinction was never a property of a
|
|
319
|
+
* repo — it is whatever separation the reader actually keeps, and a boolean
|
|
320
|
+
* leaves a third slice nowhere to go.
|
|
321
|
+
*/
|
|
322
|
+
type RepoProfile = {
|
|
323
|
+
/** Stable id — what `--profile=<name>` selects. */
|
|
324
|
+
name: string;
|
|
325
|
+
/** Shown on the toggle. Defaults to `name`. */
|
|
326
|
+
label?: string;
|
|
327
|
+
/** Which repos belong to this profile. */
|
|
328
|
+
match: (repo: string) => boolean;
|
|
329
|
+
/** Where this profile's checkouts live, if it keeps its own directory. */
|
|
330
|
+
checkoutDir?: string;
|
|
331
|
+
};
|
|
332
|
+
type InboxConfig = {
|
|
333
|
+
/**
|
|
334
|
+
* Repo ranking, best first. An entry ending in `/` matches by owner prefix,
|
|
335
|
+
* anything else must equal `owner/name` exactly — so a single repo can outrank
|
|
336
|
+
* the owner that contains it. Repos matching nothing sort last, together.
|
|
337
|
+
*/
|
|
338
|
+
repoPriority: readonly string[];
|
|
339
|
+
profiles: readonly RepoProfile[];
|
|
340
|
+
/**
|
|
341
|
+
* Fallback directory holding checkouts, for repos in no profile that names one
|
|
342
|
+
* of its own. Unset means local checkouts are not resolved at all, which is
|
|
343
|
+
* correct for a host that only ever opens things in a browser.
|
|
344
|
+
*/
|
|
345
|
+
checkoutDir?: string;
|
|
346
|
+
/**
|
|
347
|
+
* Directory name the on-disk cache lives under, inside the platform cache
|
|
348
|
+
* root. Defaults to this package's name — a library has no business claiming a
|
|
349
|
+
* directory named after whichever tool happens to embed it, and two hosts on
|
|
350
|
+
* one machine would otherwise share a cache keyed only by section name.
|
|
351
|
+
*/
|
|
352
|
+
cacheNamespace: string;
|
|
353
|
+
/**
|
|
354
|
+
* How long a cached inbox is trusted before a launch refetches. Every launch
|
|
355
|
+
* past it pays the full query — eight searches with check rollups — so this is
|
|
356
|
+
* the single knob between "always current" and "always slow, and drawing 502s
|
|
357
|
+
* from the API".
|
|
358
|
+
*
|
|
359
|
+
* It can be generous because staleness is bounded from the other end: acting
|
|
360
|
+
* on a row drops the entry outright, and `r` refetches on demand. What the TTL
|
|
361
|
+
* actually governs is how long a change made ELSEWHERE — someone approving
|
|
362
|
+
* your PR while you were not looking — can go unseen, which is a glance
|
|
363
|
+
* arriving late, not a wrong action taken.
|
|
364
|
+
*/
|
|
365
|
+
cacheTtlMs: number;
|
|
366
|
+
};
|
|
367
|
+
/** Supply the host's opinions. Call once, before rendering. */
|
|
368
|
+
declare const configureInbox: (config: Partial<InboxConfig>) => void;
|
|
369
|
+
declare const inboxConfig: () => InboxConfig;
|
|
370
|
+
/** Test seam — restores the empty defaults. */
|
|
371
|
+
declare const resetInboxConfig: () => void;
|
|
372
|
+
/** The profile a repo belongs to, or null when none claims it. */
|
|
373
|
+
declare const profileOf: (repo: string) => RepoProfile | null;
|
|
374
|
+
/**
|
|
375
|
+
* Where checkouts are searched, in order: every profile that names its own
|
|
376
|
+
* directory, then the fallback. Deduped, since the ordinary single-directory
|
|
377
|
+
* case points them all at the same place. Empty when nothing is configured —
|
|
378
|
+
* callers treat that as "not checked out locally", which it truthfully is.
|
|
379
|
+
*/
|
|
380
|
+
declare const checkoutDirs: () => string[];
|
|
381
|
+
|
|
382
|
+
type RepoFilter = {
|
|
383
|
+
/** Show only repos matching one of these. Empty or absent means show all. */
|
|
384
|
+
include?: readonly string[];
|
|
385
|
+
/** Hide repos matching one of these. Applied after include, and wins. */
|
|
386
|
+
exclude?: readonly string[];
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Whether a repo survives the filter. No include list means everything is in;
|
|
390
|
+
* exclude is applied afterwards and always wins, so a reader can take a whole
|
|
391
|
+
* org and drop one repo from it without listing the rest.
|
|
392
|
+
*/
|
|
393
|
+
declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
394
|
+
/**
|
|
395
|
+
* Parse a comma-separated pattern list, as a flag supplies it. Blank entries and
|
|
396
|
+
* surrounding whitespace are dropped, so a trailing comma or a quoted list with
|
|
397
|
+
* spaces after the commas both behave.
|
|
398
|
+
*/
|
|
399
|
+
declare const parsePatterns: (value: string) => string[];
|
|
400
|
+
|
|
401
|
+
export { type Action, ActionMenu, type AnyItem, App, COLS, type CiStatus, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, type GHDetail, type GHItem, HealthPanel, type HealthPanelProps, type InboxConfig, type InboxExtension, type JiraTransition, type RepoFilter, type RepoHeader, type RepoProfile, type Section, type ShowLess, type ShowMore, type Standing, type SubgroupHeader, type TaskRow, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
|
package/dist/index.js
CHANGED
|
@@ -606,9 +606,37 @@ var healthLegend = [
|
|
|
606
606
|
["merged", "Merged"],
|
|
607
607
|
["closed", "Closed"]
|
|
608
608
|
];
|
|
609
|
-
|
|
609
|
+
|
|
610
|
+
// src/inbox/config.ts
|
|
611
|
+
var EMPTY = {
|
|
612
|
+
repoPriority: [],
|
|
613
|
+
profiles: [],
|
|
614
|
+
cacheNamespace: "gh-ink",
|
|
615
|
+
cacheTtlMs: 6e5
|
|
616
|
+
};
|
|
617
|
+
var current = EMPTY;
|
|
618
|
+
var configureInbox = (config) => {
|
|
619
|
+
current = { ...EMPTY, ...config };
|
|
620
|
+
};
|
|
621
|
+
var inboxConfig = () => current;
|
|
622
|
+
var resetInboxConfig = () => {
|
|
623
|
+
current = EMPTY;
|
|
624
|
+
};
|
|
625
|
+
var profileOf = (repo) => current.profiles.find((p) => p.match(repo)) ?? null;
|
|
626
|
+
var checkoutDirs = () => [
|
|
627
|
+
.../* @__PURE__ */ new Set([
|
|
628
|
+
...current.profiles.flatMap((p) => p.checkoutDir ?? []),
|
|
629
|
+
...current.checkoutDir ? [current.checkoutDir] : []
|
|
630
|
+
])
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
// src/inbox/cache.ts
|
|
634
|
+
var cacheTtlMs = () => inboxConfig().cacheTtlMs;
|
|
610
635
|
var CACHE_VERSION = 2;
|
|
611
|
-
var cacheDir = () => join(
|
|
636
|
+
var cacheDir = () => join(
|
|
637
|
+
process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
|
|
638
|
+
inboxConfig().cacheNamespace
|
|
639
|
+
);
|
|
612
640
|
var cacheFile = (key) => join(cacheDir(), `${key.replace(/[^a-z0-9._-]/gi, "-")}.json`);
|
|
613
641
|
var readCache = (key) => {
|
|
614
642
|
try {
|
|
@@ -620,7 +648,7 @@ var readCache = (key) => {
|
|
|
620
648
|
return null;
|
|
621
649
|
}
|
|
622
650
|
};
|
|
623
|
-
var isFresh = (cached, now = Date.now()) => !!cached && now - cached.at <
|
|
651
|
+
var isFresh = (cached, now = Date.now()) => !!cached && now - cached.at < cacheTtlMs();
|
|
624
652
|
var writeCache = (key, data) => {
|
|
625
653
|
try {
|
|
626
654
|
mkdirSync(cacheDir(), { recursive: true });
|
|
@@ -840,14 +868,11 @@ var explainItem = (item, login) => {
|
|
|
840
868
|
];
|
|
841
869
|
};
|
|
842
870
|
var repoPriority = (repo) => {
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
return 3;
|
|
849
|
-
}
|
|
850
|
-
return repo.startsWith("kud/") ? 0 : 1;
|
|
871
|
+
const order = inboxConfig().repoPriority;
|
|
872
|
+
const i = order.findIndex(
|
|
873
|
+
(p) => p.endsWith("/") ? repo.startsWith(p) : repo === p
|
|
874
|
+
);
|
|
875
|
+
return i === -1 ? order.length : i;
|
|
851
876
|
};
|
|
852
877
|
var sortItems = (items) => [...items].sort((a, b) => {
|
|
853
878
|
const pd = repoPriority(a.repo) - repoPriority(b.repo);
|
|
@@ -996,11 +1021,11 @@ var reposInSections = (sections) => [
|
|
|
996
1021
|
sections.flatMap((s) => s.items).filter((i) => i.kind === "pr" || i.kind === "issue").map((i) => i.repo)
|
|
997
1022
|
)
|
|
998
1023
|
].sort();
|
|
999
|
-
var moveCursor = (items,
|
|
1000
|
-
let next =
|
|
1024
|
+
var moveCursor = (items, current2, dir) => {
|
|
1025
|
+
let next = current2 + dir;
|
|
1001
1026
|
while (next >= 0 && next < items.length && (items[next].kind === "repo-header" || items[next].kind === "subgroup-header"))
|
|
1002
1027
|
next += dir;
|
|
1003
|
-
if (next < 0 || next >= items.length) return
|
|
1028
|
+
if (next < 0 || next >= items.length) return current2;
|
|
1004
1029
|
return next;
|
|
1005
1030
|
};
|
|
1006
1031
|
var itemLines = (item, isFirst) => (item.kind === "repo-header" || item.kind === "subgroup-header") && !isFirst ? 2 : 1;
|
|
@@ -1049,11 +1074,8 @@ var clipboard = (text) => {
|
|
|
1049
1074
|
};
|
|
1050
1075
|
var buildCheckoutCmd = async (repoFull, branch, login) => {
|
|
1051
1076
|
const [repoOwner, repoName] = repoFull.split("/");
|
|
1052
|
-
const
|
|
1053
|
-
const
|
|
1054
|
-
const isWorkRepo = profile === "work" && (repoFull.startsWith("theorchard/") || repoFull.startsWith("kud/") && (repoName ?? "").startsWith("theorchard-"));
|
|
1055
|
-
const cloneBase = profile === "work" ? `${projects}/${isWorkRepo ? "work" : "home"}` : projects;
|
|
1056
|
-
const searchDirs = profile === "work" ? [`${projects}/work`, `${projects}/home`] : [projects];
|
|
1077
|
+
const searchDirs = checkoutDirs();
|
|
1078
|
+
const cloneBase = profileOf(repoFull)?.checkoutDir ?? inboxConfig().checkoutDir ?? "";
|
|
1057
1079
|
let repoPath = "";
|
|
1058
1080
|
outer: for (const searchDir of searchDirs) {
|
|
1059
1081
|
if (!existsSync(searchDir)) continue;
|
|
@@ -1108,9 +1130,7 @@ var buildCheckoutCmd = async (repoFull, branch, login) => {
|
|
|
1108
1130
|
return cmd;
|
|
1109
1131
|
};
|
|
1110
1132
|
var resolveRepoPath = async (repoFull) => {
|
|
1111
|
-
const
|
|
1112
|
-
const profile = process.env.OS_PROFILE ?? "";
|
|
1113
|
-
const searchDirs = profile === "work" ? [`${projects}/work`, `${projects}/home`] : [projects];
|
|
1133
|
+
const searchDirs = checkoutDirs();
|
|
1114
1134
|
for (const searchDir of searchDirs) {
|
|
1115
1135
|
if (!existsSync(searchDir)) continue;
|
|
1116
1136
|
let entries;
|
|
@@ -2936,4 +2956,18 @@ var App = ({
|
|
|
2936
2956
|
] });
|
|
2937
2957
|
};
|
|
2938
2958
|
|
|
2939
|
-
|
|
2959
|
+
// src/inbox/filter.ts
|
|
2960
|
+
var toRegExp = (pattern) => {
|
|
2961
|
+
const full = pattern.includes("/") ? pattern : `${pattern}/*`;
|
|
2962
|
+
const escaped = full.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2963
|
+
return new RegExp(`^${escaped.replace(/\*/g, "[^/]*")}$`, "i");
|
|
2964
|
+
};
|
|
2965
|
+
var matchesAny = (repo, patterns) => patterns.some((p) => toRegExp(p).test(repo));
|
|
2966
|
+
var matchesFilter = (repo, filter) => {
|
|
2967
|
+
const { include = [], exclude = [] } = filter;
|
|
2968
|
+
if (include.length > 0 && !matchesAny(repo, include)) return false;
|
|
2969
|
+
return !matchesAny(repo, exclude);
|
|
2970
|
+
};
|
|
2971
|
+
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
2972
|
+
|
|
2973
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, buildActions, buildCheckoutCmd, checkoutDirs, clipboard, configureInbox, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, inboxConfig, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, profileOf, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resetInboxConfig, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|