@kud/gh-ink 0.29.1 → 0.31.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 +1 -0
- package/dist/index.js +94 -40
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React2, { createContext, useState, useRef, useEffect, useMemo, useContext } from 'react';
|
|
2
2
|
import { useWindowSize, useInput, Text, Box } from 'ink';
|
|
3
3
|
import { colors, ScrollView, TextInput, LoadingScreen, StatusMessage, FooterHints, useListCursor, Tabs } from '@kud/ink-ui';
|
|
4
|
-
import { isPassCheck, isFailCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
|
|
4
|
+
import { isPassCheck, isFailCheck, isInconclusiveCheck, resolveThread, unresolveThread, replyToThread, rerunFailedRun, mergePr, reRequestReviewer } from '@kud/gh';
|
|
5
5
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { $ } from 'zx';
|
|
7
7
|
import { spawn } from 'child_process';
|
|
@@ -417,7 +417,7 @@ var CommentsPanel = ({
|
|
|
417
417
|
) : null
|
|
418
418
|
] });
|
|
419
419
|
};
|
|
420
|
-
var checkIcon = (c) => isPassCheck(c) ? ["\u2713", colors.success] : isFailCheck(c) ? ["\u2717", colors.error] : ["\xB7", colors.warning];
|
|
420
|
+
var checkIcon = (c) => isPassCheck(c) ? ["\u2713", colors.success] : isFailCheck(c) ? ["\u2717", colors.error] : isInconclusiveCheck(c) ? ["-", colors.warning] : ["\xB7", colors.warning];
|
|
421
421
|
var checkLabel = (c) => {
|
|
422
422
|
const name = c.context ?? c.name ?? "";
|
|
423
423
|
return c.workflowName ? `${c.workflowName} / ${name}` : name;
|
|
@@ -491,9 +491,11 @@ var HealthPanel = ({
|
|
|
491
491
|
}
|
|
492
492
|
};
|
|
493
493
|
const retrigger = () => {
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
const stuck = checks.find(
|
|
495
|
+
(c) => (isFailCheck(c) || isInconclusiveCheck(c)) && runIdOf(checkUrl(c))
|
|
496
|
+
);
|
|
497
|
+
const runId = stuck && runIdOf(checkUrl(stuck));
|
|
498
|
+
if (!runId) return flash("No Actions run to retrigger");
|
|
497
499
|
void act("retrigger CI", () => rerunFailedRun(repo, runId));
|
|
498
500
|
};
|
|
499
501
|
useInput((input, key) => {
|
|
@@ -528,7 +530,8 @@ var HealthPanel = ({
|
|
|
528
530
|
const reviewerMap = Object.fromEntries(reviewers);
|
|
529
531
|
const passed = checks.filter(isPassCheck).length;
|
|
530
532
|
const failed = checks.filter(isFailCheck).length;
|
|
531
|
-
const
|
|
533
|
+
const stale = checks.filter(isInconclusiveCheck).length;
|
|
534
|
+
const pending = checks.length - passed - failed - stale;
|
|
532
535
|
const approved = Object.values(reviewerMap).filter(
|
|
533
536
|
(s) => s === "APPROVED"
|
|
534
537
|
).length;
|
|
@@ -537,6 +540,7 @@ var HealthPanel = ({
|
|
|
537
540
|
).length;
|
|
538
541
|
const checkParts = [[`${passed} passed`, colors.success]];
|
|
539
542
|
if (failed > 0) checkParts.push([`${failed} failed`, colors.error]);
|
|
543
|
+
if (stale > 0) checkParts.push([`${stale} cancelled`, colors.warning]);
|
|
540
544
|
if (pending > 0) checkParts.push([`${pending} pending`, colors.warning]);
|
|
541
545
|
const reviewParts = [
|
|
542
546
|
approved > 0 ? [`${approved} approved`, colors.success] : ["0 approved", "white"]
|
|
@@ -823,11 +827,13 @@ var healthSentence = (item) => {
|
|
|
823
827
|
};
|
|
824
828
|
var checksSentence = (d) => {
|
|
825
829
|
if (!d) return null;
|
|
826
|
-
const
|
|
830
|
+
const stale = d.checksStale ?? 0;
|
|
831
|
+
const total = d.checksPass + d.checksFail + d.checksPending + stale;
|
|
827
832
|
if (total === 0) return "No CI checks run on it.";
|
|
828
833
|
const parts = [];
|
|
829
834
|
if (d.checksPass) parts.push(`${d.checksPass} passing`);
|
|
830
835
|
if (d.checksFail) parts.push(`${d.checksFail} failing`);
|
|
836
|
+
if (stale) parts.push(`${stale} cancelled`);
|
|
831
837
|
if (d.checksPending) parts.push(`${d.checksPending} running`);
|
|
832
838
|
return `Checks: ${parts.join(", ")}.`;
|
|
833
839
|
};
|
|
@@ -920,7 +926,14 @@ var STANDING = {
|
|
|
920
926
|
reviewed: "spoken"
|
|
921
927
|
};
|
|
922
928
|
var YOURS = {
|
|
923
|
-
authored: [
|
|
929
|
+
authored: [
|
|
930
|
+
"ci-fail",
|
|
931
|
+
"conflict",
|
|
932
|
+
"changes-req",
|
|
933
|
+
"threads",
|
|
934
|
+
"approved",
|
|
935
|
+
"draft"
|
|
936
|
+
],
|
|
924
937
|
queued: ["waiting", "pending", "threads", "approved"],
|
|
925
938
|
spoken: ["threads"]
|
|
926
939
|
};
|
|
@@ -1052,7 +1065,10 @@ var budgetNotice = (budget, now = Date.now()) => {
|
|
|
1052
1065
|
1,
|
|
1053
1066
|
Math.round((new Date(budget.resetAt).getTime() - now) / 6e4)
|
|
1054
1067
|
);
|
|
1055
|
-
return fetches <= 0 ? { label: `\u26A1 API budget spent \xB7 ${mins}m`, critical: true } : {
|
|
1068
|
+
return fetches <= 0 ? { label: `\u26A1 API budget spent \xB7 ${mins}m`, critical: true } : {
|
|
1069
|
+
label: `\u26A1 ${fetches} fetch${fetches === 1 ? "" : "es"} left`,
|
|
1070
|
+
critical: false
|
|
1071
|
+
};
|
|
1056
1072
|
};
|
|
1057
1073
|
var isChildRow = (item) => !!item && (item.kind === "show-more" || item.kind === "show-less" || "indent" in item && item.indent === true);
|
|
1058
1074
|
var treeUrls = (items, i) => {
|
|
@@ -1074,7 +1090,8 @@ var treeUrls = (items, i) => {
|
|
|
1074
1090
|
var gapsAbove = (items, i) => {
|
|
1075
1091
|
const item = items[i];
|
|
1076
1092
|
if (!item || i === 0) return false;
|
|
1077
|
-
if (item.kind === "repo-header" || item.kind === "subgroup-header")
|
|
1093
|
+
if (item.kind === "repo-header" || item.kind === "subgroup-header")
|
|
1094
|
+
return true;
|
|
1078
1095
|
if (item.kind !== "task") return false;
|
|
1079
1096
|
return isChildRow(items[i + 1]) || isChildRow(items[i - 1]);
|
|
1080
1097
|
};
|
|
@@ -1342,9 +1359,15 @@ var UNSUBSCRIBE_MUTATION = `
|
|
|
1342
1359
|
}
|
|
1343
1360
|
`;
|
|
1344
1361
|
var GH_ACTION_FAILURES = [
|
|
1345
|
-
[
|
|
1362
|
+
[
|
|
1363
|
+
/rate limit|RATE_LIMIT/i,
|
|
1364
|
+
"GitHub rate limit spent \u2014 it resets within the hour"
|
|
1365
|
+
],
|
|
1346
1366
|
[/\b401\b|not logged in|authentication/i, "gh is not authenticated"],
|
|
1347
|
-
[
|
|
1367
|
+
[
|
|
1368
|
+
/\b403\b|must have admin|not authorized|permission/i,
|
|
1369
|
+
"not permitted on this repo"
|
|
1370
|
+
],
|
|
1348
1371
|
[/\b50[0234]\b/, "GitHub's API is failing (5xx)"],
|
|
1349
1372
|
[/ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNREFUSED/i, "no route to GitHub"]
|
|
1350
1373
|
];
|
|
@@ -1468,9 +1491,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
1468
1491
|
hint: "u",
|
|
1469
1492
|
run: () => {
|
|
1470
1493
|
showFlash(`\u22EF Unsubscribing from #${item.number}\u2026`);
|
|
1471
|
-
void unsubscribeFrom(item).then(() => showFlash(`\u2713 Unsubscribed from #${item.number}`)).catch(
|
|
1472
|
-
(e) => showFlash(`\u2717 #${item.number}: ${explainGhAction(e)}`)
|
|
1473
|
-
);
|
|
1494
|
+
void unsubscribeFrom(item).then(() => showFlash(`\u2713 Unsubscribed from #${item.number}`)).catch((e) => showFlash(`\u2717 #${item.number}: ${explainGhAction(e)}`));
|
|
1474
1495
|
}
|
|
1475
1496
|
});
|
|
1476
1497
|
if (item.kind === "pr" && item.branch) {
|
|
@@ -1744,6 +1765,7 @@ var MERGED_FRAMES = ["\u2726", "\u2727", "\u2736", "\u2727"];
|
|
|
1744
1765
|
var MERGED_COLOUR = "#A371F7";
|
|
1745
1766
|
var TRANSIT_HOLD_MS = 7e3;
|
|
1746
1767
|
var NO_TRANSIENTS = /* @__PURE__ */ new Map();
|
|
1768
|
+
var NO_HOLDS = /* @__PURE__ */ new Map();
|
|
1747
1769
|
var TRANSIT_OUT_FRAMES = ["\u25C9", "\u25CE", "\u25CB", "\xB7"];
|
|
1748
1770
|
var TRANSIT_IN_FRAMES = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
1749
1771
|
var TRANSIT_COLOUR = {
|
|
@@ -1756,6 +1778,7 @@ var TRANSIT_LABEL = {
|
|
|
1756
1778
|
out: "GONE",
|
|
1757
1779
|
changed: "UPDATED"
|
|
1758
1780
|
};
|
|
1781
|
+
var LEAVING_HOLD_MS = 2500;
|
|
1759
1782
|
var TAB_MARK = "\u25CF";
|
|
1760
1783
|
var tabLabel = (label, marked, id) => marked.size === 0 ? label : `${marked.has(id) ? TAB_MARK : " "} ${label}`;
|
|
1761
1784
|
var ItemRow = ({
|
|
@@ -1764,11 +1787,13 @@ var ItemRow = ({
|
|
|
1764
1787
|
gap,
|
|
1765
1788
|
login,
|
|
1766
1789
|
merged,
|
|
1767
|
-
transient,
|
|
1790
|
+
transient: refreshMark,
|
|
1791
|
+
leaving,
|
|
1768
1792
|
lastChild,
|
|
1769
1793
|
parent,
|
|
1770
1794
|
sparkFrame = 0
|
|
1771
1795
|
}) => {
|
|
1796
|
+
const transient = leaving ? "out" : refreshMark;
|
|
1772
1797
|
if (item.kind === "repo-header")
|
|
1773
1798
|
return /* @__PURE__ */ jsx(RepoHeaderRow, { repo: item.repo, gap: gap ?? false });
|
|
1774
1799
|
if (item.kind === "subgroup-header")
|
|
@@ -2190,6 +2215,8 @@ var BrowseScreen = ({
|
|
|
2190
2215
|
skippedForBudget,
|
|
2191
2216
|
brand,
|
|
2192
2217
|
mergedUrls,
|
|
2218
|
+
leavingUrls,
|
|
2219
|
+
onLeave,
|
|
2193
2220
|
transients,
|
|
2194
2221
|
onTabChange
|
|
2195
2222
|
}) => {
|
|
@@ -2259,7 +2286,7 @@ var BrowseScreen = ({
|
|
|
2259
2286
|
)
|
|
2260
2287
|
);
|
|
2261
2288
|
}, [localSections, listHeight]);
|
|
2262
|
-
const
|
|
2289
|
+
const dismissItem = (target) => onLeave ? onLeave(target) : setLocalSections((prev) => withoutItem(prev, target));
|
|
2263
2290
|
const rawSection = localSections[safeTabIdx] ?? {
|
|
2264
2291
|
id: "empty",
|
|
2265
2292
|
label: "",
|
|
@@ -2291,7 +2318,7 @@ var BrowseScreen = ({
|
|
|
2291
2318
|
return out;
|
|
2292
2319
|
}, [localSections, transients]);
|
|
2293
2320
|
const [sparkFrame, setSparkFrame] = useState(0);
|
|
2294
|
-
const sparkling = (mergedUrls?.length ?? 0) > 0 || !!transients?.size && section.items.some((item) => transientOf(transients, item));
|
|
2321
|
+
const sparkling = (mergedUrls?.length ?? 0) > 0 || (leavingUrls?.length ?? 0) > 0 || !!transients?.size && section.items.some((item) => transientOf(transients, item));
|
|
2295
2322
|
useEffect(() => {
|
|
2296
2323
|
if (!sparkling) return;
|
|
2297
2324
|
const id = setInterval(() => setSparkFrame((f) => f + 1), MERGED_FRAME_MS);
|
|
@@ -2324,7 +2351,7 @@ var BrowseScreen = ({
|
|
|
2324
2351
|
jiraKeyRe,
|
|
2325
2352
|
jiraTransitions,
|
|
2326
2353
|
onRefresh,
|
|
2327
|
-
|
|
2354
|
+
dismissItem,
|
|
2328
2355
|
openDrillView,
|
|
2329
2356
|
{ extensions, onOpenExt, onActed }
|
|
2330
2357
|
);
|
|
@@ -2431,7 +2458,7 @@ var BrowseScreen = ({
|
|
|
2431
2458
|
item: activeItem ?? void 0,
|
|
2432
2459
|
ciJob: ciStatus?.job,
|
|
2433
2460
|
login,
|
|
2434
|
-
onRemove: (row) =>
|
|
2461
|
+
onRemove: (row) => dismissItem(row),
|
|
2435
2462
|
showFlash
|
|
2436
2463
|
});
|
|
2437
2464
|
return;
|
|
@@ -2540,7 +2567,7 @@ var BrowseScreen = ({
|
|
|
2540
2567
|
return;
|
|
2541
2568
|
}
|
|
2542
2569
|
if (input === "x" && activeItem.kind === "pr" && activeItem.standing === "queued" && login) {
|
|
2543
|
-
|
|
2570
|
+
dismissItem(activeItem);
|
|
2544
2571
|
showFlash(`\u22EF Removing you from #${activeItem.number}\u2026`);
|
|
2545
2572
|
void quietly`gh pr edit ${activeItem.number} --repo ${activeItem.repo} --remove-reviewer ${login}`.then(() => {
|
|
2546
2573
|
showFlash(`\u2713 Removed you as reviewer on #${activeItem.number}`);
|
|
@@ -2707,6 +2734,7 @@ var BrowseScreen = ({
|
|
|
2707
2734
|
active: viewStart + i === cursor,
|
|
2708
2735
|
login,
|
|
2709
2736
|
merged: (item.kind === "pr" || item.kind === "issue") && !!mergedUrls?.includes(item.url),
|
|
2737
|
+
leaving: (item.kind === "pr" || item.kind === "issue") && !!leavingUrls?.includes(item.url),
|
|
2710
2738
|
transient: transientOf(transients, item),
|
|
2711
2739
|
lastChild: "indent" in item && item.indent ? !isChildRow(section.items[viewStart + i + 1]) : void 0,
|
|
2712
2740
|
parent: item.kind === "task" && isChildRow(section.items[viewStart + i + 1]),
|
|
@@ -2807,6 +2835,7 @@ var App = ({
|
|
|
2807
2835
|
const displayedSections = useRef([]);
|
|
2808
2836
|
const [transients, setTransients] = useState(NO_TRANSIENTS);
|
|
2809
2837
|
const transitTabs = useRef(/* @__PURE__ */ new Map());
|
|
2838
|
+
const [heldSince, setHeldSince] = useState(NO_HOLDS);
|
|
2810
2839
|
const [visibleTab, setVisibleTab] = useState("");
|
|
2811
2840
|
const showData = (sections, login) => {
|
|
2812
2841
|
const before = displayedSections.current;
|
|
@@ -2861,15 +2890,31 @@ var App = ({
|
|
|
2861
2890
|
);
|
|
2862
2891
|
};
|
|
2863
2892
|
useEffect(() => {
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2893
|
+
const held = new Set(transitTabs.current.values());
|
|
2894
|
+
const arrived = state.phase === "browse" && !!visibleTab && held.has(visibleTab);
|
|
2895
|
+
setHeldSince((prev) => {
|
|
2896
|
+
const next = new Map([...prev].filter(([tab]) => held.has(tab)));
|
|
2897
|
+
if (arrived && !next.has(visibleTab)) next.set(visibleTab, Date.now());
|
|
2898
|
+
const same = next.size === prev.size && [...next.keys()].every((t) => prev.has(t));
|
|
2899
|
+
return same ? prev : next.size === 0 ? NO_HOLDS : next;
|
|
2900
|
+
});
|
|
2901
|
+
}, [transients, visibleTab, state.phase]);
|
|
2902
|
+
useEffect(() => {
|
|
2903
|
+
if (transients.size === 0 || heldSince.size === 0) return;
|
|
2904
|
+
if (state.phase !== "browse") return;
|
|
2905
|
+
const held = new Set(transitTabs.current.values());
|
|
2906
|
+
const running = [...heldSince].filter(([tab]) => held.has(tab));
|
|
2907
|
+
if (running.length === 0) return;
|
|
2908
|
+
const remaining = (at) => at + TRANSIT_HOLD_MS - Date.now();
|
|
2867
2909
|
const timer = setTimeout(
|
|
2868
|
-
() =>
|
|
2869
|
-
|
|
2910
|
+
() => {
|
|
2911
|
+
for (const [tab, at] of running)
|
|
2912
|
+
if (remaining(at) <= 0) settleTab(tab, transients);
|
|
2913
|
+
},
|
|
2914
|
+
Math.max(0, Math.min(...running.map(([, at]) => remaining(at))))
|
|
2870
2915
|
);
|
|
2871
2916
|
return () => clearTimeout(timer);
|
|
2872
|
-
}, [transients,
|
|
2917
|
+
}, [transients, heldSince, state.phase]);
|
|
2873
2918
|
const pendingSummary = useMemo(
|
|
2874
2919
|
() => pending ? summariseDiff(
|
|
2875
2920
|
diffSections(displayedSections.current, pending.sections).counts
|
|
@@ -2917,10 +2962,7 @@ var App = ({
|
|
|
2917
2962
|
}
|
|
2918
2963
|
showData(fresh.sections, fresh.login);
|
|
2919
2964
|
} else if (freshKey !== displayedKey.current) {
|
|
2920
|
-
const { counts } = diffSections(
|
|
2921
|
-
displayedSections.current,
|
|
2922
|
-
fresh.sections
|
|
2923
|
-
);
|
|
2965
|
+
const { counts } = diffSections(displayedSections.current, fresh.sections);
|
|
2924
2966
|
if (counts.added + counts.removed + counts.changed === 0)
|
|
2925
2967
|
showData(fresh.sections, fresh.login);
|
|
2926
2968
|
else setPending(fresh);
|
|
@@ -3007,8 +3049,9 @@ var App = ({
|
|
|
3007
3049
|
);
|
|
3008
3050
|
const [skippedForBudget, setSkippedForBudget] = useState(false);
|
|
3009
3051
|
const [mergedUrls, setMergedUrls] = useState([]);
|
|
3010
|
-
const
|
|
3011
|
-
|
|
3052
|
+
const [leavingUrls, setLeavingUrls] = useState([]);
|
|
3053
|
+
const holdTimers = useRef([]);
|
|
3054
|
+
useEffect(() => () => holdTimers.current.forEach(clearTimeout), []);
|
|
3012
3055
|
if (state.phase === "loading" || state.phase === "empty" || state.phase === "failed")
|
|
3013
3056
|
return /* @__PURE__ */ jsxs(
|
|
3014
3057
|
Box,
|
|
@@ -3056,17 +3099,26 @@ var App = ({
|
|
|
3056
3099
|
target: state.target
|
|
3057
3100
|
} : null;
|
|
3058
3101
|
const toBrowse = () => setState({ phase: "browse", sections: state.sections, login: state.login });
|
|
3059
|
-
const
|
|
3060
|
-
phase
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3102
|
+
const markLeaving = (target) => {
|
|
3103
|
+
if (state.phase !== "browse") toBrowse();
|
|
3104
|
+
setLeavingUrls(
|
|
3105
|
+
(prev) => prev.includes(target.url) ? prev : [...prev, target.url]
|
|
3106
|
+
);
|
|
3107
|
+
holdTimers.current.push(
|
|
3108
|
+
setTimeout(() => {
|
|
3109
|
+
setLeavingUrls((prev) => prev.filter((u) => u !== target.url));
|
|
3110
|
+
setState(
|
|
3111
|
+
(s) => s.phase === "browse" ? { ...s, sections: withoutItem(s.sections, target) } : s
|
|
3112
|
+
);
|
|
3113
|
+
}, LEAVING_HOLD_MS)
|
|
3114
|
+
);
|
|
3115
|
+
};
|
|
3064
3116
|
const markMerged = (target) => {
|
|
3065
3117
|
toBrowse();
|
|
3066
3118
|
setMergedUrls(
|
|
3067
3119
|
(prev) => prev.includes(target.url) ? prev : [...prev, target.url]
|
|
3068
3120
|
);
|
|
3069
|
-
|
|
3121
|
+
holdTimers.current.push(
|
|
3070
3122
|
setTimeout(() => {
|
|
3071
3123
|
setMergedUrls((prev) => prev.filter((u) => u !== target.url));
|
|
3072
3124
|
setState(
|
|
@@ -3097,6 +3149,8 @@ var App = ({
|
|
|
3097
3149
|
skippedForBudget,
|
|
3098
3150
|
hidden: overlay !== null,
|
|
3099
3151
|
mergedUrls,
|
|
3152
|
+
leavingUrls,
|
|
3153
|
+
onLeave: markLeaving,
|
|
3100
3154
|
transients,
|
|
3101
3155
|
onTabChange: setVisibleTab,
|
|
3102
3156
|
ciStatusState: hasCiStatus ? ciStatusState : void 0,
|
|
@@ -3130,7 +3184,7 @@ var App = ({
|
|
|
3130
3184
|
login: state.login,
|
|
3131
3185
|
onBack: toBrowse,
|
|
3132
3186
|
onRefresh: applyOrRefresh,
|
|
3133
|
-
onRemove:
|
|
3187
|
+
onRemove: markLeaving,
|
|
3134
3188
|
onMerged: markMerged
|
|
3135
3189
|
}),
|
|
3136
3190
|
overlay?.kind === "ext" && extensions?.find((e) => e.id === overlay.extId)?.body(toBrowse, overlay.target)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"react": ">=19"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@kud/gh": "0.
|
|
51
|
+
"@kud/gh": "0.8.0",
|
|
52
52
|
"@kud/ink-ui": "0.14.0",
|
|
53
53
|
"zx": "8.8.5"
|
|
54
54
|
},
|