@dacsar/prview 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/cli.js +177 -74
- package/package.json +5 -1
- package/dist/app.d.ts +0 -1
- package/dist/app.js +0 -81
- package/dist/cli.d.ts +0 -2
- package/dist/components/filter-bar.d.ts +0 -7
- package/dist/components/filter-bar.js +0 -9
- package/dist/components/header.d.ts +0 -1
- package/dist/components/header.js +0 -5
- package/dist/components/help-bar.d.ts +0 -5
- package/dist/components/help-bar.js +0 -8
- package/dist/components/loading.d.ts +0 -5
- package/dist/components/loading.js +0 -9
- package/dist/components/pr-row.d.ts +0 -7
- package/dist/components/pr-row.js +0 -10
- package/dist/components/pr-table.d.ts +0 -7
- package/dist/components/pr-table.js +0 -9
- package/dist/components/status-badge.d.ts +0 -7
- package/dist/components/status-badge.js +0 -21
- package/dist/components/tab-bar.d.ts +0 -8
- package/dist/components/tab-bar.js +0 -6
- package/dist/hooks/use-filter-sort.d.ts +0 -2
- package/dist/hooks/use-filter-sort.js +0 -24
- package/dist/hooks/use-pull-requests.d.ts +0 -10
- package/dist/hooks/use-pull-requests.js +0 -38
- package/dist/types.d.ts +0 -32
- package/dist/types.js +0 -1
- package/dist/utils/fetch-prs.d.ts +0 -4
- package/dist/utils/fetch-prs.js +0 -127
- package/dist/utils/format-time.d.ts +0 -3
- package/dist/utils/format-time.js +0 -27
- package/dist/utils/open-url.d.ts +0 -1
- package/dist/utils/open-url.js +0 -6
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -48719,6 +48719,7 @@ function Loading({ error }) {
|
|
|
48719
48719
|
|
|
48720
48720
|
// src/constants.ts
|
|
48721
48721
|
var COL = {
|
|
48722
|
+
icon: 4,
|
|
48722
48723
|
repo: 18,
|
|
48723
48724
|
status: 12,
|
|
48724
48725
|
diff: 12,
|
|
@@ -48726,6 +48727,49 @@ var COL = {
|
|
|
48726
48727
|
reviewee: 12,
|
|
48727
48728
|
reviewer: 12
|
|
48728
48729
|
};
|
|
48730
|
+
var BASE_WIDTH = 7;
|
|
48731
|
+
var OPTIONAL_WIDTH = {
|
|
48732
|
+
repo: COL.repo + 1,
|
|
48733
|
+
// 18 + separator
|
|
48734
|
+
status: COL.status + 2,
|
|
48735
|
+
// 12 + separator + marginLeft
|
|
48736
|
+
diff: COL.diff + 2,
|
|
48737
|
+
elapsed: COL.elapsed + 2,
|
|
48738
|
+
author: COL.reviewee + 2,
|
|
48739
|
+
reviewer: COL.reviewer + 2
|
|
48740
|
+
};
|
|
48741
|
+
var DROP_ORDER = [
|
|
48742
|
+
"reviewer",
|
|
48743
|
+
"author",
|
|
48744
|
+
"repo",
|
|
48745
|
+
"diff",
|
|
48746
|
+
"elapsed",
|
|
48747
|
+
"status"
|
|
48748
|
+
];
|
|
48749
|
+
var MIN_TITLE_WIDTH = 20;
|
|
48750
|
+
function computeColumnLayout(columns) {
|
|
48751
|
+
const visible = new Set(
|
|
48752
|
+
Object.keys(OPTIONAL_WIDTH)
|
|
48753
|
+
);
|
|
48754
|
+
const fixed = () => {
|
|
48755
|
+
let width = BASE_WIDTH;
|
|
48756
|
+
for (const col of visible) width += OPTIONAL_WIDTH[col];
|
|
48757
|
+
return width;
|
|
48758
|
+
};
|
|
48759
|
+
for (const col of DROP_ORDER) {
|
|
48760
|
+
if (columns - fixed() >= MIN_TITLE_WIDTH) break;
|
|
48761
|
+
visible.delete(col);
|
|
48762
|
+
}
|
|
48763
|
+
return {
|
|
48764
|
+
showRepo: visible.has("repo"),
|
|
48765
|
+
showStatus: visible.has("status"),
|
|
48766
|
+
showDiff: visible.has("diff"),
|
|
48767
|
+
showElapsed: visible.has("elapsed"),
|
|
48768
|
+
showAuthor: visible.has("author"),
|
|
48769
|
+
showReviewer: visible.has("reviewer"),
|
|
48770
|
+
titleWidth: Math.max(10, columns - fixed())
|
|
48771
|
+
};
|
|
48772
|
+
}
|
|
48729
48773
|
|
|
48730
48774
|
// src/utils/format-time.ts
|
|
48731
48775
|
function formatElapsedTime(createdAt) {
|
|
@@ -48745,9 +48789,8 @@ function formatElapsedTime(createdAt) {
|
|
|
48745
48789
|
}
|
|
48746
48790
|
function getTimeIcon(createdAt) {
|
|
48747
48791
|
const diffHours = (Date.now() - new Date(createdAt).getTime()) / (1e3 * 60 * 60);
|
|
48748
|
-
if (diffHours
|
|
48749
|
-
|
|
48750
|
-
return "\u{1F525}";
|
|
48792
|
+
if (diffHours >= 24) return "\u{1F525}";
|
|
48793
|
+
return " ";
|
|
48751
48794
|
}
|
|
48752
48795
|
function getTimeColor(createdAt) {
|
|
48753
48796
|
const now = Date.now();
|
|
@@ -48787,6 +48830,17 @@ function StatusBadge({ decision, isDraft }) {
|
|
|
48787
48830
|
// src/components/pr-row.tsx
|
|
48788
48831
|
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
|
48789
48832
|
function PrRow({ pr, isSelected }) {
|
|
48833
|
+
const { stdout } = use_stdout_default();
|
|
48834
|
+
const columns = stdout?.columns ?? 120;
|
|
48835
|
+
const {
|
|
48836
|
+
showRepo,
|
|
48837
|
+
showStatus,
|
|
48838
|
+
showDiff,
|
|
48839
|
+
showElapsed,
|
|
48840
|
+
showAuthor,
|
|
48841
|
+
showReviewer,
|
|
48842
|
+
titleWidth
|
|
48843
|
+
} = computeColumnLayout(columns);
|
|
48790
48844
|
const timeColor = getTimeColor(pr.createdAt);
|
|
48791
48845
|
const elapsed = formatElapsedTime(pr.createdAt);
|
|
48792
48846
|
const icon = getTimeIcon(pr.createdAt);
|
|
@@ -48799,42 +48853,50 @@ function PrRow({ pr, isSelected }) {
|
|
|
48799
48853
|
const extraCount = filteredReviewers.length - 1;
|
|
48800
48854
|
const reviewerText = firstReviewer ? `@${firstReviewer.login}${extraCount > 0 ? ` +${extraCount}` : ""}` : "";
|
|
48801
48855
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { backgroundColor: isSelected ? "#333333" : void 0, children: [
|
|
48802
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "red" : void 0, children: isSelected ? "
|
|
48803
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
48804
|
-
|
|
48805
|
-
|
|
48806
|
-
|
|
48856
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "red" : void 0, children: isSelected ? ">" : " " }),
|
|
48857
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.icon, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: timeColor, wrap: "truncate", children: ` ${icon} ` }) }),
|
|
48858
|
+
showRepo && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
48859
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.repo, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: !isSelected, color: selColor, wrap: "truncate", children: [
|
|
48860
|
+
repoShort,
|
|
48861
|
+
"#",
|
|
48862
|
+
pr.number
|
|
48863
|
+
] }) }),
|
|
48864
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" })
|
|
48807
48865
|
] }),
|
|
48808
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width:
|
|
48809
|
-
|
|
48810
|
-
"
|
|
48811
|
-
pr.
|
|
48812
|
-
] }) }),
|
|
48813
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "\u2502" }),
|
|
48814
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexGrow: 1, marginLeft: 1, marginRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { bold: isSelected, color: selColor, wrap: "truncate", children: pr.title }) }),
|
|
48815
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "\u2502" }),
|
|
48816
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.status, marginLeft: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatusBadge, { decision: pr.reviewDecision, isDraft: pr.isDraft }) }),
|
|
48817
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "\u2502" }),
|
|
48818
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { width: COL.diff, marginLeft: 1, justifyContent: "flex-end", children: [
|
|
48819
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "green", children: [
|
|
48820
|
-
"+",
|
|
48821
|
-
pr.additions
|
|
48822
|
-
] }),
|
|
48823
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: " " }),
|
|
48824
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "red", children: [
|
|
48825
|
-
"-",
|
|
48826
|
-
pr.deletions
|
|
48827
|
-
] })
|
|
48866
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: titleWidth, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { bold: isSelected, color: selColor, wrap: "truncate", children: pr.title }) }),
|
|
48867
|
+
showStatus && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
48868
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48869
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.status, marginLeft: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatusBadge, { decision: pr.reviewDecision, isDraft: pr.isDraft }) })
|
|
48828
48870
|
] }),
|
|
48829
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
48830
|
-
|
|
48831
|
-
|
|
48832
|
-
|
|
48833
|
-
|
|
48834
|
-
|
|
48835
|
-
|
|
48836
|
-
|
|
48837
|
-
|
|
48871
|
+
showDiff && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
48872
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48873
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.diff, marginLeft: 1, justifyContent: "flex-end", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { wrap: "truncate", children: [
|
|
48874
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "green", children: [
|
|
48875
|
+
"+",
|
|
48876
|
+
pr.additions
|
|
48877
|
+
] }),
|
|
48878
|
+
" ",
|
|
48879
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "red", children: [
|
|
48880
|
+
"-",
|
|
48881
|
+
pr.deletions
|
|
48882
|
+
] })
|
|
48883
|
+
] }) })
|
|
48884
|
+
] }),
|
|
48885
|
+
showElapsed && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
48886
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48887
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.elapsed, marginLeft: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: timeColor, wrap: "truncate", children: elapsed }) })
|
|
48888
|
+
] }),
|
|
48889
|
+
showAuthor && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
48890
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48891
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.reviewee, marginLeft: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { dimColor: true, wrap: "truncate", children: [
|
|
48892
|
+
"@",
|
|
48893
|
+
pr.author
|
|
48894
|
+
] }) })
|
|
48895
|
+
] }),
|
|
48896
|
+
showReviewer && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
48897
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48898
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.reviewer, marginLeft: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, wrap: "truncate", children: reviewerText }) })
|
|
48899
|
+
] })
|
|
48838
48900
|
] });
|
|
48839
48901
|
}
|
|
48840
48902
|
|
|
@@ -48865,6 +48927,28 @@ function PrTable({ prs, selectedIndex, maxRows }) {
|
|
|
48865
48927
|
|
|
48866
48928
|
// src/components/status-bar.tsx
|
|
48867
48929
|
var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
|
|
48930
|
+
var HINTS = [
|
|
48931
|
+
{ key: "Tab", label: "switch", keep: 6 },
|
|
48932
|
+
{ key: "j/k", label: "move", keep: 3 },
|
|
48933
|
+
{ key: "Enter", label: "open", keep: 2 },
|
|
48934
|
+
{ key: "y", label: "copy", keep: 4 },
|
|
48935
|
+
{ key: "/", label: "filter", keep: 5 },
|
|
48936
|
+
{ key: "r", label: "refresh", keep: 7 },
|
|
48937
|
+
{ key: "q", label: "quit", keep: 1 }
|
|
48938
|
+
];
|
|
48939
|
+
var hintWidth = (h) => h.key.length + 1 + h.label.length;
|
|
48940
|
+
function visibleHints(budget) {
|
|
48941
|
+
const byPriority = [...HINTS].sort((a, b) => a.keep - b.keep);
|
|
48942
|
+
const shown = /* @__PURE__ */ new Set();
|
|
48943
|
+
let used = 0;
|
|
48944
|
+
for (const h of byPriority) {
|
|
48945
|
+
const width = hintWidth(h) + (shown.size > 0 ? 1 : 0);
|
|
48946
|
+
if (used + width > budget) break;
|
|
48947
|
+
used += width;
|
|
48948
|
+
shown.add(h.key);
|
|
48949
|
+
}
|
|
48950
|
+
return HINTS.filter((h) => shown.has(h.key));
|
|
48951
|
+
}
|
|
48868
48952
|
function StatusBar({
|
|
48869
48953
|
isFilterActive,
|
|
48870
48954
|
filter,
|
|
@@ -48873,10 +48957,15 @@ function StatusBar({
|
|
|
48873
48957
|
reviewCount,
|
|
48874
48958
|
myCount,
|
|
48875
48959
|
isFilterMode,
|
|
48876
|
-
loading
|
|
48960
|
+
loading,
|
|
48961
|
+
copiedMessage
|
|
48877
48962
|
}) {
|
|
48963
|
+
const { stdout } = use_stdout_default();
|
|
48964
|
+
const columns = stdout?.columns ?? 80;
|
|
48878
48965
|
const isReview = activeTab === "review-requested";
|
|
48879
48966
|
const tabLabel = isReview ? `To Review (${reviewCount})` : `My PRs (${myCount})`;
|
|
48967
|
+
const reserved = 2 + tabLabel.length + (loading ? 13 : 0) + 2;
|
|
48968
|
+
const hints = visibleHints(columns - reserved);
|
|
48880
48969
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
48881
48970
|
Box_default,
|
|
48882
48971
|
{
|
|
@@ -48889,7 +48978,10 @@ function StatusBar({
|
|
|
48889
48978
|
paddingX: 1,
|
|
48890
48979
|
justifyContent: "space-between",
|
|
48891
48980
|
children: [
|
|
48892
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { children:
|
|
48981
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { children: copiedMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "green", children: [
|
|
48982
|
+
"\u2713 ",
|
|
48983
|
+
copiedMessage
|
|
48984
|
+
] }) : isFilterActive || filter ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
48893
48985
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", children: "/" }),
|
|
48894
48986
|
isFilterActive ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
48895
48987
|
TextInput,
|
|
@@ -48910,38 +49002,11 @@ function StatusBar({
|
|
|
48910
49002
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "gray", children: "Esc" }),
|
|
48911
49003
|
" ",
|
|
48912
49004
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "cancel" })
|
|
48913
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
48914
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.
|
|
48915
|
-
|
|
48916
|
-
|
|
48917
|
-
|
|
48918
|
-
] }),
|
|
48919
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
|
|
48920
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "white", children: "j/k" }),
|
|
48921
|
-
" ",
|
|
48922
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "move" })
|
|
48923
|
-
] }),
|
|
48924
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
|
|
48925
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "white", children: "Enter" }),
|
|
48926
|
-
" ",
|
|
48927
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "open" })
|
|
48928
|
-
] }),
|
|
48929
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
|
|
48930
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "white", children: "/" }),
|
|
48931
|
-
" ",
|
|
48932
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "filter" })
|
|
48933
|
-
] }),
|
|
48934
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
|
|
48935
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "white", children: "r" }),
|
|
48936
|
-
" ",
|
|
48937
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "refresh" })
|
|
48938
|
-
] }),
|
|
48939
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
|
|
48940
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "white", children: "q" }),
|
|
48941
|
-
" ",
|
|
48942
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: "quit" })
|
|
48943
|
-
] })
|
|
48944
|
-
] }) })
|
|
49005
|
+
] }) : hints.map((h) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
|
|
49006
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "white", children: h.key }),
|
|
49007
|
+
" ",
|
|
49008
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { dimColor: true, children: h.label })
|
|
49009
|
+
] }, h.key)) })
|
|
48945
49010
|
]
|
|
48946
49011
|
}
|
|
48947
49012
|
);
|
|
@@ -49130,10 +49195,18 @@ function usePullRequests() {
|
|
|
49130
49195
|
return { reviewRequested, myPRs, loading, error, refresh: load };
|
|
49131
49196
|
}
|
|
49132
49197
|
|
|
49133
|
-
// src/utils/
|
|
49198
|
+
// src/utils/copy-to-clipboard.ts
|
|
49134
49199
|
import { execFile as execFile2 } from "node:child_process";
|
|
49200
|
+
function copyToClipboard(text) {
|
|
49201
|
+
const child = execFile2("pbcopy", () => {
|
|
49202
|
+
});
|
|
49203
|
+
child.stdin?.end(text);
|
|
49204
|
+
}
|
|
49205
|
+
|
|
49206
|
+
// src/utils/open-url.ts
|
|
49207
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
49135
49208
|
function openUrl(url2) {
|
|
49136
|
-
|
|
49209
|
+
execFile3("open", [url2], () => {
|
|
49137
49210
|
});
|
|
49138
49211
|
}
|
|
49139
49212
|
|
|
@@ -49148,8 +49221,29 @@ function App2() {
|
|
|
49148
49221
|
const [selectedIndex, setSelectedIndex] = (0, import_react62.useState)(0);
|
|
49149
49222
|
const [filter, setFilter] = (0, import_react62.useState)("");
|
|
49150
49223
|
const [isFilterMode, setIsFilterMode] = (0, import_react62.useState)(false);
|
|
49224
|
+
const [copiedMessage, setCopiedMessage] = (0, import_react62.useState)("");
|
|
49225
|
+
const copiedTimer = (0, import_react62.useRef)(null);
|
|
49226
|
+
const notifyCopied = (0, import_react62.useCallback)(() => {
|
|
49227
|
+
setCopiedMessage("Copied URL to clipboard");
|
|
49228
|
+
if (copiedTimer.current) {
|
|
49229
|
+
clearTimeout(copiedTimer.current);
|
|
49230
|
+
}
|
|
49231
|
+
copiedTimer.current = setTimeout(() => setCopiedMessage(""), 2e3);
|
|
49232
|
+
}, []);
|
|
49151
49233
|
const currentPRs = activeTab === "review-requested" ? reviewRequested : myPRs;
|
|
49152
49234
|
const filteredPRs = useFilterSort(currentPRs, filter);
|
|
49235
|
+
const [, setResizeTick] = (0, import_react62.useState)(0);
|
|
49236
|
+
(0, import_react62.useEffect)(() => {
|
|
49237
|
+
if (!stdout) return;
|
|
49238
|
+
const onResize = () => {
|
|
49239
|
+
stdout.write("\x1B[2J\x1B[H");
|
|
49240
|
+
setResizeTick((n) => n + 1);
|
|
49241
|
+
};
|
|
49242
|
+
stdout.on("resize", onResize);
|
|
49243
|
+
return () => {
|
|
49244
|
+
stdout.off("resize", onResize);
|
|
49245
|
+
};
|
|
49246
|
+
}, [stdout]);
|
|
49153
49247
|
const clampIndex = (0, import_react62.useCallback)(
|
|
49154
49248
|
(index) => Math.max(0, Math.min(index, filteredPRs.length - 1)),
|
|
49155
49249
|
[filteredPRs.length]
|
|
@@ -49191,6 +49285,14 @@ function App2() {
|
|
|
49191
49285
|
}
|
|
49192
49286
|
return;
|
|
49193
49287
|
}
|
|
49288
|
+
if (input === "y") {
|
|
49289
|
+
const pr = filteredPRs[selectedIndex];
|
|
49290
|
+
if (pr) {
|
|
49291
|
+
copyToClipboard(pr.url);
|
|
49292
|
+
notifyCopied();
|
|
49293
|
+
}
|
|
49294
|
+
return;
|
|
49295
|
+
}
|
|
49194
49296
|
if (input === "/") {
|
|
49195
49297
|
setIsFilterMode(true);
|
|
49196
49298
|
return;
|
|
@@ -49241,7 +49343,8 @@ function App2() {
|
|
|
49241
49343
|
reviewCount: reviewRequested.length,
|
|
49242
49344
|
myCount: myPRs.length,
|
|
49243
49345
|
isFilterMode,
|
|
49244
|
-
loading
|
|
49346
|
+
loading,
|
|
49347
|
+
copiedMessage
|
|
49245
49348
|
}
|
|
49246
49349
|
)
|
|
49247
49350
|
] });
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dacsar/prview",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "TUI tool to check PRs across repositories",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/dac-sar/prview.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"bin": {
|
|
7
11
|
"pv": "./dist/cli.js"
|
package/dist/app.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function App(): import("react/jsx-runtime").JSX.Element;
|
package/dist/app.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback } from 'react';
|
|
3
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
-
import { usePullRequests } from './hooks/use-pull-requests.js';
|
|
5
|
-
import { useFilterSort } from './hooks/use-filter-sort.js';
|
|
6
|
-
import { openUrl } from './utils/open-url.js';
|
|
7
|
-
import { TabBar } from './components/tab-bar.js';
|
|
8
|
-
import { PrTable } from './components/pr-table.js';
|
|
9
|
-
import { FilterBar } from './components/filter-bar.js';
|
|
10
|
-
import { HelpBar } from './components/help-bar.js';
|
|
11
|
-
import { Loading } from './components/loading.js';
|
|
12
|
-
export function App() {
|
|
13
|
-
const { exit } = useApp();
|
|
14
|
-
const { reviewRequested, myPRs, loading, error, refresh } = usePullRequests();
|
|
15
|
-
const [activeTab, setActiveTab] = useState('review-requested');
|
|
16
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
17
|
-
const [filter, setFilter] = useState('');
|
|
18
|
-
const [isFilterMode, setIsFilterMode] = useState(false);
|
|
19
|
-
const [sort, setSort] = useState({ key: 'time', direction: 'desc' });
|
|
20
|
-
const currentPRs = activeTab === 'review-requested' ? reviewRequested : myPRs;
|
|
21
|
-
const filteredPRs = useFilterSort(currentPRs, filter, sort);
|
|
22
|
-
const clampIndex = useCallback((index) => Math.max(0, Math.min(index, filteredPRs.length - 1)), [filteredPRs.length]);
|
|
23
|
-
useInput((_input, key) => {
|
|
24
|
-
if (key.escape) {
|
|
25
|
-
setIsFilterMode(false);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
}, { isActive: isFilterMode });
|
|
29
|
-
useInput((input, key) => {
|
|
30
|
-
if (input === 'q') {
|
|
31
|
-
exit();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (key.tab) {
|
|
35
|
-
setActiveTab(prev => prev === 'review-requested' ? 'my-prs' : 'review-requested');
|
|
36
|
-
setSelectedIndex(0);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (input === 'j' || key.downArrow) {
|
|
40
|
-
setSelectedIndex(prev => clampIndex(prev + 1));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (input === 'k' || key.upArrow) {
|
|
44
|
-
setSelectedIndex(prev => clampIndex(prev - 1));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (key.return) {
|
|
48
|
-
const pr = filteredPRs[selectedIndex];
|
|
49
|
-
if (pr) {
|
|
50
|
-
openUrl(pr.url);
|
|
51
|
-
}
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (input === 's') {
|
|
55
|
-
setSort(prev => {
|
|
56
|
-
if (prev.key === 'title') {
|
|
57
|
-
return { key: 'time', direction: prev.direction };
|
|
58
|
-
}
|
|
59
|
-
if (prev.key === 'time' && prev.direction === 'desc') {
|
|
60
|
-
return { key: 'time', direction: 'asc' };
|
|
61
|
-
}
|
|
62
|
-
return { key: 'title', direction: 'asc' };
|
|
63
|
-
});
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (input === '/') {
|
|
67
|
-
setIsFilterMode(true);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (key.escape) {
|
|
71
|
-
setFilter('');
|
|
72
|
-
setIsFilterMode(false);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (input === 'r') {
|
|
76
|
-
refresh();
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
}, { isActive: !isFilterMode });
|
|
80
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TabBar, { activeTab: activeTab, reviewCount: reviewRequested.length, myCount: myPRs.length }), error && (_jsx(Box, { paddingX: 2, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), loading && filteredPRs.length === 0 ? (_jsx(Loading, { error: error })) : (_jsx(PrTable, { prs: filteredPRs, selectedIndex: selectedIndex })), _jsx(FilterBar, { isActive: isFilterMode, filter: filter, onFilterChange: setFilter }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { dimColor: true, children: ["Sort: ", sort.key, " (", sort.direction, ")"] }) }), _jsx(HelpBar, { isFilterMode: isFilterMode })] }));
|
|
81
|
-
}
|
package/dist/cli.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { TextInput } from '@inkjs/ui';
|
|
4
|
-
export function FilterBar({ isActive, filter, onFilterChange }) {
|
|
5
|
-
if (!isActive && !filter) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellow", children: "/" }), isActive ? (_jsx(TextInput, { defaultValue: filter, onChange: onFilterChange, placeholder: "Filter by title, repo, author, reviewer..." })) : (_jsxs(Text, { dimColor: true, children: [" ", filter] }))] }));
|
|
9
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function Header(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
export function Header() {
|
|
4
|
-
return (_jsx(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, justifyContent: "center", children: _jsx(Text, { bold: true, color: "cyan", children: "PR Checker" }) }));
|
|
5
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
export function HelpBar({ isFilterMode }) {
|
|
4
|
-
if (isFilterMode) {
|
|
5
|
-
return (_jsx(Box, { paddingX: 1, borderStyle: "single", borderColor: "gray", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "Esc" }), " cancel filter"] }) }));
|
|
6
|
-
}
|
|
7
|
-
return (_jsxs(Box, { paddingX: 1, gap: 2, borderStyle: "single", borderColor: "gray", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "Tab" }), " switch"] }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "j/k" }), " move"] }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "Enter" }), " open"] }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "/" }), " filter"] }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "s" }), " sort"] }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "r" }), " refresh"] }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, children: "q" }), " quit"] })] }));
|
|
8
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from 'ink';
|
|
3
|
-
import { Spinner } from '@inkjs/ui';
|
|
4
|
-
export function Loading({ error }) {
|
|
5
|
-
if (error) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Spinner, { label: "Fetching pull requests..." }) }));
|
|
9
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { formatElapsedTime, getTimeColor } from '../utils/format-time.js';
|
|
4
|
-
import { StatusBadge } from './status-badge.js';
|
|
5
|
-
export function PrRow({ pr, isSelected }) {
|
|
6
|
-
const timeColor = getTimeColor(pr.createdAt);
|
|
7
|
-
const elapsed = formatElapsedTime(pr.createdAt);
|
|
8
|
-
const repoShort = pr.repository.split('/')[1] ?? pr.repository;
|
|
9
|
-
return (_jsxs(Box, { children: [_jsx(Text, { bold: isSelected, color: isSelected ? 'cyan' : undefined, children: isSelected ? '▸ ' : ' ' }), _jsx(Box, { width: 20, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [repoShort, "#", pr.number] }) }), _jsx(Box, { flexGrow: 1, marginRight: 1, children: _jsx(Text, { bold: isSelected, wrap: "truncate", children: pr.title }) }), _jsx(Box, { width: 10, children: _jsx(StatusBadge, { decision: pr.reviewDecision, isDraft: pr.isDraft }) }), _jsx(Box, { width: 8, justifyContent: "flex-end", children: _jsxs(Text, { color: "green", children: ["+", pr.additions] }) }), _jsx(Box, { width: 8, justifyContent: "flex-end", children: _jsxs(Text, { color: "red", children: ["-", pr.deletions] }) }), _jsx(Box, { width: 6, justifyContent: "flex-end", marginLeft: 1, children: _jsxs(Text, { dimColor: true, children: ["@", pr.author] }) }), _jsx(Box, { width: 10, justifyContent: "flex-end", marginLeft: 1, children: _jsx(Text, { color: timeColor, children: elapsed }) })] }));
|
|
10
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { PrRow } from './pr-row.js';
|
|
4
|
-
export function PrTable({ prs, selectedIndex }) {
|
|
5
|
-
if (prs.length === 0) {
|
|
6
|
-
return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { dimColor: true, children: "No pull requests found." }) }));
|
|
7
|
-
}
|
|
8
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: prs.map((pr, index) => (_jsx(PrRow, { pr: pr, isSelected: index === selectedIndex }, pr.url))) }));
|
|
9
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
export function StatusBadge({ decision, isDraft }) {
|
|
4
|
-
if (isDraft) {
|
|
5
|
-
return _jsx(Text, { color: "gray", children: " DRAFT " });
|
|
6
|
-
}
|
|
7
|
-
switch (decision) {
|
|
8
|
-
case 'APPROVED': {
|
|
9
|
-
return _jsx(Text, { color: "green", children: " APPROVED " });
|
|
10
|
-
}
|
|
11
|
-
case 'CHANGES_REQUESTED': {
|
|
12
|
-
return _jsx(Text, { color: "red", children: " CHANGES " });
|
|
13
|
-
}
|
|
14
|
-
case 'REVIEW_REQUIRED': {
|
|
15
|
-
return _jsx(Text, { color: "yellow", children: " PENDING " });
|
|
16
|
-
}
|
|
17
|
-
default: {
|
|
18
|
-
return _jsx(Text, { color: "yellow", children: " PENDING " });
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
export function TabBar({ activeTab, reviewCount, myCount }) {
|
|
4
|
-
const isReview = activeTab === 'review-requested';
|
|
5
|
-
return (_jsxs(Box, { gap: 2, paddingX: 1, children: [_jsxs(Text, { bold: isReview, color: isReview ? 'cyan' : undefined, dimColor: !isReview, children: [isReview ? '▸ ' : ' ', "Review Requested (", reviewCount, ")"] }), _jsxs(Text, { bold: !isReview, color: !isReview ? 'magenta' : undefined, dimColor: isReview, children: [!isReview ? '▸ ' : ' ', "My PRs (", myCount, ")"] })] }));
|
|
6
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
export function useFilterSort(prs, filter, sort) {
|
|
3
|
-
return useMemo(() => {
|
|
4
|
-
let result = prs;
|
|
5
|
-
if (filter) {
|
|
6
|
-
const lower = filter.toLowerCase();
|
|
7
|
-
result = result.filter(pr => pr.title.toLowerCase().includes(lower)
|
|
8
|
-
|| pr.repository.toLowerCase().includes(lower)
|
|
9
|
-
|| pr.author.toLowerCase().includes(lower)
|
|
10
|
-
|| pr.reviewers.some(r => r.login.toLowerCase().includes(lower)));
|
|
11
|
-
}
|
|
12
|
-
const sorted = [...result].sort((a, b) => {
|
|
13
|
-
if (sort.key === 'title') {
|
|
14
|
-
return a.title.localeCompare(b.title);
|
|
15
|
-
}
|
|
16
|
-
// Sort by time (createdAt)
|
|
17
|
-
return (new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
18
|
-
});
|
|
19
|
-
if (sort.direction === 'desc') {
|
|
20
|
-
sorted.reverse();
|
|
21
|
-
}
|
|
22
|
-
return sorted;
|
|
23
|
-
}, [prs, filter, sort]);
|
|
24
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { PullRequest } from '../types.js';
|
|
2
|
-
type UsePullRequestsResult = {
|
|
3
|
-
reviewRequested: PullRequest[];
|
|
4
|
-
myPRs: PullRequest[];
|
|
5
|
-
loading: boolean;
|
|
6
|
-
error: string | null;
|
|
7
|
-
refresh: () => void;
|
|
8
|
-
};
|
|
9
|
-
export declare function usePullRequests(): UsePullRequestsResult;
|
|
10
|
-
export {};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import { fetchReviewRequested, fetchMyPRs } from '../utils/fetch-prs.js';
|
|
3
|
-
const REFRESH_INTERVAL = 60000;
|
|
4
|
-
export function usePullRequests() {
|
|
5
|
-
const [reviewRequested, setReviewRequested] = useState([]);
|
|
6
|
-
const [myPRs, setMyPRs] = useState([]);
|
|
7
|
-
const [loading, setLoading] = useState(true);
|
|
8
|
-
const [error, setError] = useState(null);
|
|
9
|
-
const load = useCallback(async () => {
|
|
10
|
-
setLoading(true);
|
|
11
|
-
setError(null);
|
|
12
|
-
try {
|
|
13
|
-
const [rr, my] = await Promise.all([
|
|
14
|
-
fetchReviewRequested(),
|
|
15
|
-
fetchMyPRs(),
|
|
16
|
-
]);
|
|
17
|
-
setReviewRequested(rr);
|
|
18
|
-
setMyPRs(my);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
22
|
-
setError(message);
|
|
23
|
-
}
|
|
24
|
-
finally {
|
|
25
|
-
setLoading(false);
|
|
26
|
-
}
|
|
27
|
-
}, []);
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
void load();
|
|
30
|
-
const timer = setInterval(() => {
|
|
31
|
-
void load();
|
|
32
|
-
}, REFRESH_INTERVAL);
|
|
33
|
-
return () => {
|
|
34
|
-
clearInterval(timer);
|
|
35
|
-
};
|
|
36
|
-
}, [load]);
|
|
37
|
-
return { reviewRequested, myPRs, loading, error, refresh: load };
|
|
38
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export type ReviewDecision = 'APPROVED' | 'CHANGES_REQUESTED' | 'REVIEW_REQUIRED' | '';
|
|
2
|
-
export type ReviewState = 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'PENDING' | 'DISMISSED';
|
|
3
|
-
export type Reviewer = {
|
|
4
|
-
login: string;
|
|
5
|
-
state: ReviewState;
|
|
6
|
-
};
|
|
7
|
-
export type Label = {
|
|
8
|
-
name: string;
|
|
9
|
-
color: string;
|
|
10
|
-
};
|
|
11
|
-
export type PullRequest = {
|
|
12
|
-
number: number;
|
|
13
|
-
title: string;
|
|
14
|
-
url: string;
|
|
15
|
-
createdAt: string;
|
|
16
|
-
repository: string;
|
|
17
|
-
author: string;
|
|
18
|
-
reviewers: Reviewer[];
|
|
19
|
-
reviewDecision: ReviewDecision;
|
|
20
|
-
isDraft: boolean;
|
|
21
|
-
additions: number;
|
|
22
|
-
deletions: number;
|
|
23
|
-
labels: Label[];
|
|
24
|
-
branch: string;
|
|
25
|
-
};
|
|
26
|
-
export type Tab = 'review-requested' | 'my-prs';
|
|
27
|
-
export type SortKey = 'title' | 'time';
|
|
28
|
-
export type SortDirection = 'asc' | 'desc';
|
|
29
|
-
export type SortState = {
|
|
30
|
-
key: SortKey;
|
|
31
|
-
direction: SortDirection;
|
|
32
|
-
};
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/fetch-prs.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
const QUERY = `
|
|
3
|
-
query($q: String!) {
|
|
4
|
-
search(query: $q, type: ISSUE, first: 50) {
|
|
5
|
-
nodes {
|
|
6
|
-
... on PullRequest {
|
|
7
|
-
number
|
|
8
|
-
title
|
|
9
|
-
url
|
|
10
|
-
createdAt
|
|
11
|
-
isDraft
|
|
12
|
-
additions
|
|
13
|
-
deletions
|
|
14
|
-
headRefName
|
|
15
|
-
reviewDecision
|
|
16
|
-
author {
|
|
17
|
-
login
|
|
18
|
-
}
|
|
19
|
-
repository {
|
|
20
|
-
nameWithOwner
|
|
21
|
-
}
|
|
22
|
-
labels(first: 10) {
|
|
23
|
-
nodes {
|
|
24
|
-
name
|
|
25
|
-
color
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
latestReviews(first: 10) {
|
|
29
|
-
nodes {
|
|
30
|
-
author {
|
|
31
|
-
login
|
|
32
|
-
}
|
|
33
|
-
state
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
reviewRequests(first: 10) {
|
|
37
|
-
nodes {
|
|
38
|
-
requestedReviewer {
|
|
39
|
-
... on User {
|
|
40
|
-
login
|
|
41
|
-
}
|
|
42
|
-
... on Team {
|
|
43
|
-
name
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
`;
|
|
53
|
-
function parseNode(node) {
|
|
54
|
-
const reviewers = [];
|
|
55
|
-
for (const review of node.latestReviews.nodes) {
|
|
56
|
-
if (review.author) {
|
|
57
|
-
reviewers.push({
|
|
58
|
-
login: review.author.login,
|
|
59
|
-
state: review.state,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
for (const request of node.reviewRequests.nodes) {
|
|
64
|
-
const login = request.requestedReviewer?.login ?? request.requestedReviewer?.name;
|
|
65
|
-
if (login && !reviewers.some(r => r.login === login)) {
|
|
66
|
-
reviewers.push({ login, state: 'PENDING' });
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const labels = node.labels.nodes.map(l => ({
|
|
70
|
-
name: l.name,
|
|
71
|
-
color: l.color,
|
|
72
|
-
}));
|
|
73
|
-
return {
|
|
74
|
-
number: node.number,
|
|
75
|
-
title: node.title,
|
|
76
|
-
url: node.url,
|
|
77
|
-
createdAt: node.createdAt,
|
|
78
|
-
repository: node.repository.nameWithOwner,
|
|
79
|
-
author: node.author?.login ?? 'unknown',
|
|
80
|
-
reviewers,
|
|
81
|
-
reviewDecision: node.reviewDecision ?? '',
|
|
82
|
-
isDraft: node.isDraft,
|
|
83
|
-
additions: node.additions,
|
|
84
|
-
deletions: node.deletions,
|
|
85
|
-
labels,
|
|
86
|
-
branch: node.headRefName,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function runGh(args, stdin) {
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
const child = execFile('gh', args, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => {
|
|
92
|
-
if (error) {
|
|
93
|
-
reject(error);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
resolve(stdout);
|
|
97
|
-
});
|
|
98
|
-
if (stdin && child.stdin) {
|
|
99
|
-
child.stdin.write(stdin);
|
|
100
|
-
child.stdin.end();
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
function runGraphQL(searchQuery) {
|
|
105
|
-
const body = JSON.stringify({
|
|
106
|
-
query: QUERY,
|
|
107
|
-
variables: { q: searchQuery },
|
|
108
|
-
});
|
|
109
|
-
return runGh(['api', 'graphql', '--input', '-'], body).then(result => JSON.parse(result));
|
|
110
|
-
}
|
|
111
|
-
export async function checkAuth() {
|
|
112
|
-
try {
|
|
113
|
-
await runGh(['auth', 'status']);
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
export async function fetchReviewRequested() {
|
|
121
|
-
const json = await runGraphQL('is:pr is:open review-requested:@me');
|
|
122
|
-
return json.data.search.nodes.map(node => parseNode(node));
|
|
123
|
-
}
|
|
124
|
-
export async function fetchMyPRs() {
|
|
125
|
-
const json = await runGraphQL('is:pr is:open author:@me');
|
|
126
|
-
return json.data.search.nodes.map(node => parseNode(node));
|
|
127
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export function formatElapsedTime(createdAt) {
|
|
2
|
-
const now = Date.now();
|
|
3
|
-
const created = new Date(createdAt).getTime();
|
|
4
|
-
const diffMs = now - created;
|
|
5
|
-
const minutes = Math.floor(diffMs / (1000 * 60));
|
|
6
|
-
const hours = Math.floor(minutes / 60);
|
|
7
|
-
const days = Math.floor(hours / 24);
|
|
8
|
-
if (days > 0) {
|
|
9
|
-
return `${String(days)}d ago`;
|
|
10
|
-
}
|
|
11
|
-
if (hours > 0) {
|
|
12
|
-
return `${String(hours)}h ago`;
|
|
13
|
-
}
|
|
14
|
-
return `${String(minutes)}m ago`;
|
|
15
|
-
}
|
|
16
|
-
export function getTimeColor(createdAt) {
|
|
17
|
-
const now = Date.now();
|
|
18
|
-
const created = new Date(createdAt).getTime();
|
|
19
|
-
const diffHours = (now - created) / (1000 * 60 * 60);
|
|
20
|
-
if (diffHours > 48) {
|
|
21
|
-
return 'red';
|
|
22
|
-
}
|
|
23
|
-
if (diffHours > 24) {
|
|
24
|
-
return 'yellow';
|
|
25
|
-
}
|
|
26
|
-
return 'green';
|
|
27
|
-
}
|
package/dist/utils/open-url.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function openUrl(url: string): void;
|