@kud/gh-ink 0.18.0 → 0.20.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 CHANGED
@@ -172,8 +172,20 @@ type Action = {
172
172
  subActions?: Action[];
173
173
  };
174
174
  type JiraTransition = {
175
+ /** What the action menu shows. Yours to word; nothing matches on it. */
175
176
  label: string;
176
- state: string;
177
+ /**
178
+ * The TRANSITION name, passed verbatim to `jira issue move`. Not the
179
+ * destination status, and the two are routinely different strings — jira-cli
180
+ * matches `t.name.toLowerCase() === wanted`, so a status name here matches no
181
+ * transition, the move silently does nothing, and the row simply does not
182
+ * budge. This field was called `state`, which is what invited exactly that:
183
+ * cockpit had `{ label: "UAT", state: "UAT" }` against an ACC workflow whose
184
+ * transition is named "Ready for QA" and whose status is "In Testing (QA)" —
185
+ * no "UAT" anywhere, and no error either.
186
+ */
187
+ transition: string;
188
+ /** Offered as a second step when the transition asks for a resolution. */
177
189
  resolutions?: string[];
178
190
  };
179
191
  declare const relativeTime: (iso: string) => string;
@@ -299,4 +311,59 @@ declare const writeCache: (key: string, data: {
299
311
  login: string;
300
312
  }) => void;
301
313
 
302
- 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 InboxExtension, type JiraTransition, type RepoHeader, type Section, type ShowLess, type ShowMore, type Standing, type SubgroupHeader, type TaskRow, buildActions, buildCheckoutCmd, clipboard, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, maxViewStart, moveCursor, openInTab, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
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
+ /** Supply the host's opinions. Call once, before rendering. */
355
+ declare const configureInbox: (config: Partial<InboxConfig>) => void;
356
+ declare const inboxConfig: () => InboxConfig;
357
+ /** Test seam — restores the empty defaults. */
358
+ declare const resetInboxConfig: () => void;
359
+ /** The profile a repo belongs to, or null when none claims it. */
360
+ declare const profileOf: (repo: string) => RepoProfile | null;
361
+ /**
362
+ * Where checkouts are searched, in order: every profile that names its own
363
+ * directory, then the fallback. Deduped, since the ordinary single-directory
364
+ * case points them all at the same place. Empty when nothing is configured —
365
+ * callers treat that as "not checked out locally", which it truthfully is.
366
+ */
367
+ declare const checkoutDirs: () => string[];
368
+
369
+ 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 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, maxViewStart, moveCursor, openInTab, 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,36 @@ var healthLegend = [
606
606
  ["merged", "Merged"],
607
607
  ["closed", "Closed"]
608
608
  ];
609
+
610
+ // src/inbox/config.ts
611
+ var EMPTY = {
612
+ repoPriority: [],
613
+ profiles: [],
614
+ cacheNamespace: "gh-ink"
615
+ };
616
+ var current = EMPTY;
617
+ var configureInbox = (config) => {
618
+ current = { ...EMPTY, ...config };
619
+ };
620
+ var inboxConfig = () => current;
621
+ var resetInboxConfig = () => {
622
+ current = EMPTY;
623
+ };
624
+ var profileOf = (repo) => current.profiles.find((p) => p.match(repo)) ?? null;
625
+ var checkoutDirs = () => [
626
+ .../* @__PURE__ */ new Set([
627
+ ...current.profiles.flatMap((p) => p.checkoutDir ?? []),
628
+ ...current.checkoutDir ? [current.checkoutDir] : []
629
+ ])
630
+ ];
631
+
632
+ // src/inbox/cache.ts
609
633
  var CACHE_TTL_MS = 12e4;
610
634
  var CACHE_VERSION = 2;
611
- var cacheDir = () => join(process.env.XDG_CACHE_HOME || join(homedir(), ".cache"), "ambre");
635
+ var cacheDir = () => join(
636
+ process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
637
+ inboxConfig().cacheNamespace
638
+ );
612
639
  var cacheFile = (key) => join(cacheDir(), `${key.replace(/[^a-z0-9._-]/gi, "-")}.json`);
613
640
  var readCache = (key) => {
614
641
  try {
@@ -840,14 +867,11 @@ var explainItem = (item, login) => {
840
867
  ];
841
868
  };
842
869
  var repoPriority = (repo) => {
843
- const profile = process.env.OS_PROFILE ?? "";
844
- if (profile === "work") {
845
- if (repo === "theorchard/orchardgo") return 0;
846
- if (repo.startsWith("theorchard/")) return 1;
847
- if (repo.startsWith("kud/")) return 2;
848
- return 3;
849
- }
850
- return repo.startsWith("kud/") ? 0 : 1;
870
+ const order = inboxConfig().repoPriority;
871
+ const i = order.findIndex(
872
+ (p) => p.endsWith("/") ? repo.startsWith(p) : repo === p
873
+ );
874
+ return i === -1 ? order.length : i;
851
875
  };
852
876
  var sortItems = (items) => [...items].sort((a, b) => {
853
877
  const pd = repoPriority(a.repo) - repoPriority(b.repo);
@@ -996,11 +1020,11 @@ var reposInSections = (sections) => [
996
1020
  sections.flatMap((s) => s.items).filter((i) => i.kind === "pr" || i.kind === "issue").map((i) => i.repo)
997
1021
  )
998
1022
  ].sort();
999
- var moveCursor = (items, current, dir) => {
1000
- let next = current + dir;
1023
+ var moveCursor = (items, current2, dir) => {
1024
+ let next = current2 + dir;
1001
1025
  while (next >= 0 && next < items.length && (items[next].kind === "repo-header" || items[next].kind === "subgroup-header"))
1002
1026
  next += dir;
1003
- if (next < 0 || next >= items.length) return current;
1027
+ if (next < 0 || next >= items.length) return current2;
1004
1028
  return next;
1005
1029
  };
1006
1030
  var itemLines = (item, isFirst) => (item.kind === "repo-header" || item.kind === "subgroup-header") && !isFirst ? 2 : 1;
@@ -1049,11 +1073,8 @@ var clipboard = (text) => {
1049
1073
  };
1050
1074
  var buildCheckoutCmd = async (repoFull, branch, login) => {
1051
1075
  const [repoOwner, repoName] = repoFull.split("/");
1052
- const projects = process.env.PROJECTS_DIR ?? `${process.env.HOME}/Projects`;
1053
- const profile = process.env.OS_PROFILE ?? "";
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];
1076
+ const searchDirs = checkoutDirs();
1077
+ const cloneBase = profileOf(repoFull)?.checkoutDir ?? inboxConfig().checkoutDir ?? "";
1057
1078
  let repoPath = "";
1058
1079
  outer: for (const searchDir of searchDirs) {
1059
1080
  if (!existsSync(searchDir)) continue;
@@ -1108,9 +1129,7 @@ var buildCheckoutCmd = async (repoFull, branch, login) => {
1108
1129
  return cmd;
1109
1130
  };
1110
1131
  var resolveRepoPath = async (repoFull) => {
1111
- const projects = process.env.PROJECTS_DIR ?? `${process.env.HOME}/Projects`;
1112
- const profile = process.env.OS_PROFILE ?? "";
1113
- const searchDirs = profile === "work" ? [`${projects}/work`, `${projects}/home`] : [projects];
1132
+ const searchDirs = checkoutDirs();
1114
1133
  for (const searchDir of searchDirs) {
1115
1134
  if (!existsSync(searchDir)) continue;
1116
1135
  let entries;
@@ -1309,7 +1328,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1309
1328
  run: () => {
1310
1329
  },
1311
1330
  subActions: jiraTransitions.map(
1312
- ({ label, state, resolutions }) => resolutions && resolutions.length > 0 ? {
1331
+ ({ label, transition, resolutions }) => resolutions && resolutions.length > 0 ? {
1313
1332
  label,
1314
1333
  hint: "",
1315
1334
  run: () => {
@@ -1319,7 +1338,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1319
1338
  hint: "",
1320
1339
  run: () => {
1321
1340
  showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
1322
- void quietly`jira issue move ${item.ticket} ${state} --resolution ${resolution}`.then(() => {
1341
+ void quietly`jira issue move ${item.ticket} ${transition} --resolution ${resolution}`.then(() => {
1323
1342
  showFlash(`\u2713 ${label} \xB7 ${resolution}`);
1324
1343
  ext?.onActed?.();
1325
1344
  }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
@@ -1330,7 +1349,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
1330
1349
  hint: "",
1331
1350
  run: () => {
1332
1351
  showFlash(`\u22EF Moving to ${label}\u2026`);
1333
- void quietly`jira issue move ${item.ticket} ${state}`.then(() => {
1352
+ void quietly`jira issue move ${item.ticket} ${transition}`.then(() => {
1334
1353
  showFlash(`\u2713 Moved to ${label}`);
1335
1354
  ext?.onActed?.();
1336
1355
  }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
@@ -2427,7 +2446,7 @@ var BrowseScreen = ({
2427
2446
  if (input === "t" && activeItem && activeItem.kind === "task" && activeItem.ticket && jiraTransitions && jiraTransitions.length > 0) {
2428
2447
  const jiraKey = activeItem.ticket;
2429
2448
  const actions = jiraTransitions.map(
2430
- ({ label, state, resolutions }) => resolutions && resolutions.length > 0 ? {
2449
+ ({ label, transition, resolutions }) => resolutions && resolutions.length > 0 ? {
2431
2450
  label,
2432
2451
  hint: "",
2433
2452
  run: () => {
@@ -2438,7 +2457,7 @@ var BrowseScreen = ({
2438
2457
  run: () => {
2439
2458
  menu.close();
2440
2459
  showFlash(`\u22EF ${label} \xB7 ${resolution}\u2026`);
2441
- quietly`jira issue move ${jiraKey} ${state} --resolution ${resolution}`.then(() => {
2460
+ quietly`jira issue move ${jiraKey} ${transition} --resolution ${resolution}`.then(() => {
2442
2461
  showFlash(`\u2713 ${label} \xB7 ${resolution}`);
2443
2462
  onActed?.();
2444
2463
  }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
@@ -2450,7 +2469,7 @@ var BrowseScreen = ({
2450
2469
  run: () => {
2451
2470
  menu.close();
2452
2471
  showFlash(`\u22EF Moving to ${label}\u2026`);
2453
- quietly`jira issue move ${jiraKey} ${state}`.then(() => {
2472
+ quietly`jira issue move ${jiraKey} ${transition}`.then(() => {
2454
2473
  showFlash(`\u2713 Moved to ${label}`);
2455
2474
  onActed?.();
2456
2475
  }).catch(() => showFlash(`\u2717 Move to ${label} failed`));
@@ -2936,4 +2955,4 @@ var App = ({
2936
2955
  ] });
2937
2956
  };
2938
2957
 
2939
- export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, buildActions, buildCheckoutCmd, clipboard, drillCmd, explainItem, filterByOrigin, filterByRepos, filterBySearch, fitCount, healthColor, healthDisplay, healthGlyph, healthLegend, insertRepoHeaders, itermRun, jumpToRepo, jumpToRepoPane, layoutGHItems, maxViewStart, moveCursor, openInTab, readCache, relativeTime, renderMarkdown, repoPriority, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sortByRecency, sortItems, toCiStatusState, topLevelCount, truncate, useActionMenu, whoseMove, windowCount, withHeaders, withoutItem, writeCache };
2958
+ 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, maxViewStart, moveCursor, openInTab, 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.18.0",
3
+ "version": "0.20.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",