@dacsar/prview 1.0.0 → 1.0.1
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 +16 -16
- 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,7 @@ var COL = {
|
|
|
48726
48727
|
reviewee: 12,
|
|
48727
48728
|
reviewer: 12
|
|
48728
48729
|
};
|
|
48730
|
+
var FIXED_COLS_WIDTH = 92;
|
|
48729
48731
|
|
|
48730
48732
|
// src/utils/format-time.ts
|
|
48731
48733
|
function formatElapsedTime(createdAt) {
|
|
@@ -48745,9 +48747,8 @@ function formatElapsedTime(createdAt) {
|
|
|
48745
48747
|
}
|
|
48746
48748
|
function getTimeIcon(createdAt) {
|
|
48747
48749
|
const diffHours = (Date.now() - new Date(createdAt).getTime()) / (1e3 * 60 * 60);
|
|
48748
|
-
if (diffHours
|
|
48749
|
-
|
|
48750
|
-
return "\u{1F525}";
|
|
48750
|
+
if (diffHours >= 24) return "\u{1F525}";
|
|
48751
|
+
return " ";
|
|
48751
48752
|
}
|
|
48752
48753
|
function getTimeColor(createdAt) {
|
|
48753
48754
|
const now = Date.now();
|
|
@@ -48787,6 +48788,9 @@ function StatusBadge({ decision, isDraft }) {
|
|
|
48787
48788
|
// src/components/pr-row.tsx
|
|
48788
48789
|
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
|
48789
48790
|
function PrRow({ pr, isSelected }) {
|
|
48791
|
+
const { stdout } = use_stdout_default();
|
|
48792
|
+
const columns = stdout?.columns ?? 120;
|
|
48793
|
+
const titleWidth = Math.max(10, columns - FIXED_COLS_WIDTH);
|
|
48790
48794
|
const timeColor = getTimeColor(pr.createdAt);
|
|
48791
48795
|
const elapsed = formatElapsedTime(pr.createdAt);
|
|
48792
48796
|
const icon = getTimeIcon(pr.createdAt);
|
|
@@ -48799,22 +48803,18 @@ function PrRow({ pr, isSelected }) {
|
|
|
48799
48803
|
const extraCount = filteredReviewers.length - 1;
|
|
48800
48804
|
const reviewerText = firstReviewer ? `@${firstReviewer.login}${extraCount > 0 ? ` +${extraCount}` : ""}` : "";
|
|
48801
48805
|
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
|
-
icon,
|
|
48806
|
-
" "
|
|
48807
|
-
] }),
|
|
48806
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: isSelected ? "red" : void 0, children: isSelected ? ">" : " " }),
|
|
48807
|
+
/* @__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} ` }) }),
|
|
48808
48808
|
/* @__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: [
|
|
48809
48809
|
repoShort,
|
|
48810
48810
|
"#",
|
|
48811
48811
|
pr.number
|
|
48812
48812
|
] }) }),
|
|
48813
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "
|
|
48814
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, {
|
|
48815
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "
|
|
48813
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48814
|
+
/* @__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 }) }),
|
|
48815
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48816
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: "
|
|
48817
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48818
48818
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Box_default, { width: COL.diff, marginLeft: 1, justifyContent: "flex-end", children: [
|
|
48819
48819
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Text, { color: "green", children: [
|
|
48820
48820
|
"+",
|
|
@@ -48826,14 +48826,14 @@ function PrRow({ pr, isSelected }) {
|
|
|
48826
48826
|
pr.deletions
|
|
48827
48827
|
] })
|
|
48828
48828
|
] }),
|
|
48829
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "
|
|
48829
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48830
48830
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { width: COL.elapsed, marginLeft: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { color: timeColor, children: elapsed }) }),
|
|
48831
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "
|
|
48831
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48832
48832
|
/* @__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: [
|
|
48833
48833
|
"@",
|
|
48834
48834
|
pr.author
|
|
48835
48835
|
] }) }),
|
|
48836
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "
|
|
48836
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: "|" }),
|
|
48837
48837
|
/* @__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 }) })
|
|
48838
48838
|
] });
|
|
48839
48839
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dacsar/prview",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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;
|