clairo 2.2.0 → 3.0.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 +2 -0
- package/dist/cli.js +551 -124
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ dashboard tui for github PRs, jira tickets, and daily logs.
|
|
|
11
11
|
- view open PRs with status, reviews, and checks
|
|
12
12
|
- see full PR details with description, labels, and assignees
|
|
13
13
|
- create new PRs from the terminal
|
|
14
|
+
- browse all PRs across the repo with state, search, and status filters
|
|
15
|
+
- checkout PRs directly from the detail view
|
|
14
16
|
- jira integration
|
|
15
17
|
- auto ticket detection based on branch name
|
|
16
18
|
- link tickets and change status from the terminal
|
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import meow from "meow";
|
|
5
5
|
|
|
6
6
|
// src/app.tsx
|
|
7
|
-
import { useCallback as
|
|
8
|
-
import { Box as
|
|
7
|
+
import { useCallback as useCallback12, useMemo as useMemo4, useState as useState23 } from "react";
|
|
8
|
+
import { Box as Box23, Text as Text22, useApp, useInput as useInput19 } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/components/github/GitHubView.tsx
|
|
11
11
|
import { useCallback as useCallback8, useEffect as useEffect9, useRef as useRef5, useState as useState12 } from "react";
|
|
@@ -194,7 +194,7 @@ function resolveCheckStatus(check) {
|
|
|
194
194
|
const conclusion = check.conclusion ?? check.state;
|
|
195
195
|
if (conclusion === "SUCCESS") return "success";
|
|
196
196
|
if (conclusion === "FAILURE" || conclusion === "ERROR") return "failure";
|
|
197
|
-
if (conclusion === "SKIPPED" || conclusion === "NEUTRAL") return "skipped";
|
|
197
|
+
if (conclusion === "SKIPPED" || conclusion === "NEUTRAL" || conclusion === "CANCELLED") return "skipped";
|
|
198
198
|
if (conclusion === "PENDING" || check.status === "IN_PROGRESS" || check.status === "QUEUED" || check.status === "WAITING")
|
|
199
199
|
return "pending";
|
|
200
200
|
return "pending";
|
|
@@ -261,7 +261,7 @@ async function listPRsForBranch(branch, repo) {
|
|
|
261
261
|
errorType: "not_authenticated"
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
-
const fields = "number,title,state,author,createdAt,isDraft";
|
|
264
|
+
const fields = "number,title,state,author,createdAt,isDraft,reviewDecision,statusCheckRollup";
|
|
265
265
|
try {
|
|
266
266
|
const { stdout } = await execAsync(`gh pr view --json ${fields} 2>/dev/null`);
|
|
267
267
|
const pr = JSON.parse(stdout);
|
|
@@ -1146,25 +1146,45 @@ async function getBoardIssues(auth, boardId, opts) {
|
|
|
1146
1146
|
function escapeJql(text) {
|
|
1147
1147
|
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1148
1148
|
}
|
|
1149
|
-
function
|
|
1149
|
+
function createSearchClause(searchText) {
|
|
1150
1150
|
const escaped = escapeJql(searchText);
|
|
1151
|
-
|
|
1151
|
+
if (/^[A-Z]+-\d+$/i.test(searchText)) {
|
|
1152
|
+
return `(key = "${escaped}" OR text ~ "${escaped}")`;
|
|
1153
|
+
}
|
|
1154
|
+
if (/^\d+$/.test(searchText)) {
|
|
1155
|
+
return `(key ~ "*-${escaped}" OR text ~ "${escaped}")`;
|
|
1156
|
+
}
|
|
1157
|
+
return `text ~ "${escaped}"`;
|
|
1158
|
+
}
|
|
1159
|
+
function appendTextSearch(jql, searchText) {
|
|
1160
|
+
return `(${jql}) AND ${createSearchClause(searchText)}`;
|
|
1161
|
+
}
|
|
1162
|
+
function createAssigneeClause(filter) {
|
|
1163
|
+
if (filter === "unassigned") return "assignee is EMPTY";
|
|
1164
|
+
return "assignee = currentUser()";
|
|
1152
1165
|
}
|
|
1153
1166
|
async function fetchViewIssues(auth, view, opts) {
|
|
1154
|
-
const { searchText, ...pageOpts } = opts ?? {};
|
|
1167
|
+
const { searchText, assigneeFilter, ...pageOpts } = opts ?? {};
|
|
1155
1168
|
switch (view.source.type) {
|
|
1156
1169
|
case "jql": {
|
|
1157
|
-
|
|
1170
|
+
let jql = view.source.jql;
|
|
1171
|
+
if (searchText) jql = appendTextSearch(jql, searchText);
|
|
1172
|
+
if (assigneeFilter) jql = `(${jql}) AND ${createAssigneeClause(assigneeFilter)}`;
|
|
1158
1173
|
return searchIssues(auth, jql, pageOpts);
|
|
1159
1174
|
}
|
|
1160
1175
|
case "filter": {
|
|
1161
1176
|
const filterResult = await getFilterJql(auth, view.source.filterId);
|
|
1162
1177
|
if (!filterResult.success) return filterResult;
|
|
1163
|
-
|
|
1178
|
+
let jql = filterResult.data;
|
|
1179
|
+
if (searchText) jql = appendTextSearch(jql, searchText);
|
|
1180
|
+
if (assigneeFilter) jql = `(${jql}) AND ${createAssigneeClause(assigneeFilter)}`;
|
|
1164
1181
|
return searchIssues(auth, jql, pageOpts);
|
|
1165
1182
|
}
|
|
1166
1183
|
case "board": {
|
|
1167
|
-
const
|
|
1184
|
+
const clauses = [];
|
|
1185
|
+
if (searchText) clauses.push(createSearchClause(searchText));
|
|
1186
|
+
if (assigneeFilter) clauses.push(createAssigneeClause(assigneeFilter));
|
|
1187
|
+
const jql = clauses.length > 0 ? clauses.join(" AND ") : void 0;
|
|
1168
1188
|
return getBoardIssues(auth, view.source.boardId, { ...pageOpts, jql });
|
|
1169
1189
|
}
|
|
1170
1190
|
}
|
|
@@ -1537,6 +1557,7 @@ import open from "open";
|
|
|
1537
1557
|
import { useRef as useRef2 } from "react";
|
|
1538
1558
|
import { Box as Box3, Text as Text3, useInput, useStdout } from "ink";
|
|
1539
1559
|
import { ScrollView } from "ink-scroll-view";
|
|
1560
|
+
import Spinner from "ink-spinner";
|
|
1540
1561
|
|
|
1541
1562
|
// src/components/ui/Badge.tsx
|
|
1542
1563
|
import { Text } from "ink";
|
|
@@ -1681,10 +1702,9 @@ function renderInlineToString(tokens) {
|
|
|
1681
1702
|
|
|
1682
1703
|
// src/components/github/PRDetailsBox.tsx
|
|
1683
1704
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1684
|
-
function PRDetailsBox({ pr, loading, error, isActive }) {
|
|
1705
|
+
function PRDetailsBox({ pr, loading, error, isActive, title = "[3] PR Details" }) {
|
|
1685
1706
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1686
1707
|
const scrollRef = useRef2(null);
|
|
1687
|
-
const title = "[3] PR Details";
|
|
1688
1708
|
const borderColor = isActive ? "yellow" : void 0;
|
|
1689
1709
|
const displayTitle = pr ? `${title} - #${pr.number}` : title;
|
|
1690
1710
|
const reviewDisplay = resolveReviewDisplay((pr == null ? void 0 : pr.reviewDecision) ?? null);
|
|
@@ -1724,7 +1744,10 @@ function PRDetailsBox({ pr, loading, error, isActive }) {
|
|
|
1724
1744
|
borderTop: false,
|
|
1725
1745
|
borderColor,
|
|
1726
1746
|
children: /* @__PURE__ */ jsx4(ScrollView, { ref: scrollRef, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
|
|
1727
|
-
loading && /* @__PURE__ */
|
|
1747
|
+
loading && /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
|
|
1748
|
+
/* @__PURE__ */ jsx4(Spinner, { type: "dots" }),
|
|
1749
|
+
" Loading details..."
|
|
1750
|
+
] }),
|
|
1728
1751
|
error && /* @__PURE__ */ jsx4(Text3, { color: "red", children: error }),
|
|
1729
1752
|
!loading && !error && !pr && /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "Select a PR to view details" }),
|
|
1730
1753
|
!loading && !error && pr && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
@@ -2075,13 +2098,13 @@ function useLogs() {
|
|
|
2075
2098
|
import { useCallback as useCallback6, useState as useState7 } from "react";
|
|
2076
2099
|
function useModal() {
|
|
2077
2100
|
const [modalType, setModalType] = useState7("none");
|
|
2078
|
-
const
|
|
2101
|
+
const open7 = useCallback6((type) => setModalType(type), []);
|
|
2079
2102
|
const close = useCallback6(() => setModalType("none"), []);
|
|
2080
2103
|
const isOpen = modalType !== "none";
|
|
2081
2104
|
return {
|
|
2082
2105
|
type: modalType,
|
|
2083
2106
|
isOpen,
|
|
2084
|
-
open:
|
|
2107
|
+
open: open7,
|
|
2085
2108
|
close
|
|
2086
2109
|
};
|
|
2087
2110
|
}
|
|
@@ -3047,18 +3070,18 @@ var proto = Object.defineProperties(() => {
|
|
|
3047
3070
|
}
|
|
3048
3071
|
}
|
|
3049
3072
|
});
|
|
3050
|
-
var createStyler = (
|
|
3073
|
+
var createStyler = (open7, close, parent) => {
|
|
3051
3074
|
let openAll;
|
|
3052
3075
|
let closeAll;
|
|
3053
3076
|
if (parent === void 0) {
|
|
3054
|
-
openAll =
|
|
3077
|
+
openAll = open7;
|
|
3055
3078
|
closeAll = close;
|
|
3056
3079
|
} else {
|
|
3057
|
-
openAll = parent.openAll +
|
|
3080
|
+
openAll = parent.openAll + open7;
|
|
3058
3081
|
closeAll = close + parent.closeAll;
|
|
3059
3082
|
}
|
|
3060
3083
|
return {
|
|
3061
|
-
open:
|
|
3084
|
+
open: open7,
|
|
3062
3085
|
close,
|
|
3063
3086
|
openAll,
|
|
3064
3087
|
closeAll,
|
|
@@ -3767,7 +3790,7 @@ import { useCallback as useCallback9, useEffect as useEffect11, useMemo as useMe
|
|
|
3767
3790
|
import { TitledBox as TitledBox4 } from "@mishieck/ink-titled-box";
|
|
3768
3791
|
import { Box as Box10, Text as Text11, useInput as useInput8 } from "ink";
|
|
3769
3792
|
import { ScrollView as ScrollView6 } from "ink-scroll-view";
|
|
3770
|
-
import
|
|
3793
|
+
import Spinner3 from "ink-spinner";
|
|
3771
3794
|
|
|
3772
3795
|
// src/components/jira-browser/JiraIssueDetailView.tsx
|
|
3773
3796
|
import open3 from "open";
|
|
@@ -3775,7 +3798,7 @@ import { useEffect as useEffect10, useRef as useRef6, useState as useState14 } f
|
|
|
3775
3798
|
import { Box as Box9, Text as Text10, useInput as useInput7 } from "ink";
|
|
3776
3799
|
import { ScrollView as ScrollView5 } from "ink-scroll-view";
|
|
3777
3800
|
import SelectInput from "ink-select-input";
|
|
3778
|
-
import
|
|
3801
|
+
import Spinner2 from "ink-spinner";
|
|
3779
3802
|
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3780
3803
|
function JiraIssueDetailView({
|
|
3781
3804
|
issueKey,
|
|
@@ -3783,6 +3806,7 @@ function JiraIssueDetailView({
|
|
|
3783
3806
|
auth,
|
|
3784
3807
|
myAccountId,
|
|
3785
3808
|
myDisplayName,
|
|
3809
|
+
onFetchCurrentUser,
|
|
3786
3810
|
isActive,
|
|
3787
3811
|
onClose,
|
|
3788
3812
|
onIssueUpdated,
|
|
@@ -3844,16 +3868,27 @@ function JiraIssueDetailView({
|
|
|
3844
3868
|
setActionLoading(null);
|
|
3845
3869
|
};
|
|
3846
3870
|
const handleAssignToMe = async () => {
|
|
3847
|
-
if (!myAccountId || !myDisplayName) return;
|
|
3848
3871
|
setActionLoading("Assigning...");
|
|
3849
3872
|
setActionError(null);
|
|
3850
|
-
|
|
3873
|
+
let accountId = myAccountId;
|
|
3874
|
+
let displayName = myDisplayName;
|
|
3875
|
+
if (!accountId || !displayName) {
|
|
3876
|
+
const user = await onFetchCurrentUser();
|
|
3877
|
+
if (!user) {
|
|
3878
|
+
setActionError("Failed to get current user");
|
|
3879
|
+
setActionLoading(null);
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
accountId = user.accountId;
|
|
3883
|
+
displayName = user.displayName;
|
|
3884
|
+
}
|
|
3885
|
+
const result = await assignIssue(auth, issueKey, accountId);
|
|
3851
3886
|
if (result.success) {
|
|
3852
|
-
const assignee = { accountId
|
|
3887
|
+
const assignee = { accountId, displayName };
|
|
3853
3888
|
setDetail((prev) => prev ? { ...prev, fields: { ...prev.fields, assignee } } : prev);
|
|
3854
3889
|
onIssueUpdated(issueKey, { assignee });
|
|
3855
|
-
duckEvents.emit("jira:assigned", { ticketKey: issueKey, assignee:
|
|
3856
|
-
logJiraAssigneeChanged(issueKey, issueSummary, "assigned",
|
|
3890
|
+
duckEvents.emit("jira:assigned", { ticketKey: issueKey, assignee: displayName });
|
|
3891
|
+
logJiraAssigneeChanged(issueKey, issueSummary, "assigned", displayName);
|
|
3857
3892
|
onLogUpdated == null ? void 0 : onLogUpdated();
|
|
3858
3893
|
} else {
|
|
3859
3894
|
setActionError(result.error);
|
|
@@ -3906,7 +3941,7 @@ function JiraIssueDetailView({
|
|
|
3906
3941
|
if (input === "s" && !actionLoading) {
|
|
3907
3942
|
openTransitionPicker();
|
|
3908
3943
|
}
|
|
3909
|
-
if (input === "a" && !actionLoading
|
|
3944
|
+
if (input === "a" && !actionLoading) {
|
|
3910
3945
|
handleAssignToMe();
|
|
3911
3946
|
}
|
|
3912
3947
|
if (input === "A" && !actionLoading) {
|
|
@@ -3922,7 +3957,7 @@ function JiraIssueDetailView({
|
|
|
3922
3957
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", flexGrow: 1, children: [
|
|
3923
3958
|
/* @__PURE__ */ jsxs9(Box9, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
|
|
3924
3959
|
loading && /* @__PURE__ */ jsx11(Box9, { paddingX: 1, children: /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
|
|
3925
|
-
/* @__PURE__ */ jsx11(
|
|
3960
|
+
/* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
|
|
3926
3961
|
" Loading issue details..."
|
|
3927
3962
|
] }) }),
|
|
3928
3963
|
error && /* @__PURE__ */ jsx11(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text10, { color: "red", children: error }) }),
|
|
@@ -3979,13 +4014,13 @@ function JiraIssueDetailView({
|
|
|
3979
4014
|
] }),
|
|
3980
4015
|
/* @__PURE__ */ jsxs9(Box9, { paddingX: 1, flexDirection: "column", children: [
|
|
3981
4016
|
actionLoading && /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
|
|
3982
|
-
/* @__PURE__ */ jsx11(
|
|
4017
|
+
/* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
|
|
3983
4018
|
" ",
|
|
3984
4019
|
actionLoading
|
|
3985
4020
|
] }),
|
|
3986
4021
|
actionError && /* @__PURE__ */ jsx11(Text10, { color: "red", children: actionError }),
|
|
3987
4022
|
transitionsLoading && /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
|
|
3988
|
-
/* @__PURE__ */ jsx11(
|
|
4023
|
+
/* @__PURE__ */ jsx11(Spinner2, { type: "dots" }),
|
|
3989
4024
|
" Loading transitions..."
|
|
3990
4025
|
] }),
|
|
3991
4026
|
!actionLoading && !transitionsLoading && mode === "normal" && /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "Esc close \xB7 j/k scroll \xB7 s status \xB7 a assign \xB7 A unassign \xB7 o open \xB7 y copy" })
|
|
@@ -4054,6 +4089,7 @@ function JiraSavedViewBrowserBox({
|
|
|
4054
4089
|
auth,
|
|
4055
4090
|
myAccountId,
|
|
4056
4091
|
myDisplayName,
|
|
4092
|
+
onFetchCurrentUser,
|
|
4057
4093
|
isActive,
|
|
4058
4094
|
onInputModeChange,
|
|
4059
4095
|
onLogUpdated
|
|
@@ -4074,22 +4110,10 @@ function JiraSavedViewBrowserBox({
|
|
|
4074
4110
|
const title = "[6] Issues";
|
|
4075
4111
|
const borderColor = isActive ? "yellow" : void 0;
|
|
4076
4112
|
const displayTitle = view ? `${title} - ${view.name}` : title;
|
|
4077
|
-
const filteredIssues = useMemo2(() => {
|
|
4078
|
-
if (assigneeFilter === "unassigned") {
|
|
4079
|
-
return issues.filter((issue) => !issue.fields.assignee);
|
|
4080
|
-
}
|
|
4081
|
-
if (assigneeFilter === "me" && myAccountId) {
|
|
4082
|
-
return issues.filter((issue) => {
|
|
4083
|
-
var _a;
|
|
4084
|
-
return ((_a = issue.fields.assignee) == null ? void 0 : _a.accountId) === myAccountId;
|
|
4085
|
-
});
|
|
4086
|
-
}
|
|
4087
|
-
return issues;
|
|
4088
|
-
}, [issues, assigneeFilter, myAccountId]);
|
|
4089
4113
|
const rows = useMemo2(() => {
|
|
4090
|
-
const groups = groupBySprint(
|
|
4114
|
+
const groups = groupBySprint(issues);
|
|
4091
4115
|
return buildRows(groups);
|
|
4092
|
-
}, [
|
|
4116
|
+
}, [issues]);
|
|
4093
4117
|
const navigableIndices = useMemo2(
|
|
4094
4118
|
() => rows.map((r, i) => r.type === "issue" ? i : -1).filter((i) => i >= 0),
|
|
4095
4119
|
[rows]
|
|
@@ -4104,14 +4128,19 @@ function JiraSavedViewBrowserBox({
|
|
|
4104
4128
|
}, [rows, navigableIndices, highlightedIndex]);
|
|
4105
4129
|
const hasMore = issues.length < total;
|
|
4106
4130
|
const doFetch = useCallback9(
|
|
4107
|
-
async (search, startAt = 0, append = false) => {
|
|
4131
|
+
async (search, filter, startAt = 0, append = false) => {
|
|
4108
4132
|
if (!view || !auth) return;
|
|
4109
4133
|
setLoading(true);
|
|
4110
4134
|
setError(null);
|
|
4135
|
+
if (!append) {
|
|
4136
|
+
setIssues([]);
|
|
4137
|
+
setTotal(0);
|
|
4138
|
+
}
|
|
4111
4139
|
const result = await fetchViewIssues(auth, view, {
|
|
4112
4140
|
startAt,
|
|
4113
4141
|
maxResults: 50,
|
|
4114
|
-
searchText: search || void 0
|
|
4142
|
+
searchText: search || void 0,
|
|
4143
|
+
assigneeFilter: filter === "all" ? void 0 : filter
|
|
4115
4144
|
});
|
|
4116
4145
|
if (result.success) {
|
|
4117
4146
|
setIssues((prev) => append ? [...prev, ...result.data.issues] : result.data.issues);
|
|
@@ -4132,7 +4161,8 @@ function JiraSavedViewBrowserBox({
|
|
|
4132
4161
|
if (view && auth) {
|
|
4133
4162
|
setSearchText("");
|
|
4134
4163
|
setInputText("");
|
|
4135
|
-
|
|
4164
|
+
setAssigneeFilter("all");
|
|
4165
|
+
doFetch("", "all");
|
|
4136
4166
|
} else {
|
|
4137
4167
|
setIssues([]);
|
|
4138
4168
|
setTotal(0);
|
|
@@ -4176,7 +4206,7 @@ function JiraSavedViewBrowserBox({
|
|
|
4176
4206
|
const newSearch = inputText.trim();
|
|
4177
4207
|
if (newSearch !== searchText) {
|
|
4178
4208
|
setSearchText(newSearch);
|
|
4179
|
-
doFetch(newSearch);
|
|
4209
|
+
doFetch(newSearch, assigneeFilter);
|
|
4180
4210
|
}
|
|
4181
4211
|
return;
|
|
4182
4212
|
}
|
|
@@ -4219,31 +4249,33 @@ function JiraSavedViewBrowserBox({
|
|
|
4219
4249
|
return;
|
|
4220
4250
|
}
|
|
4221
4251
|
if (input === "u") {
|
|
4222
|
-
|
|
4252
|
+
const newFilter = assigneeFilter === "unassigned" ? "all" : "unassigned";
|
|
4253
|
+
setAssigneeFilter(newFilter);
|
|
4254
|
+
doFetch(searchText, newFilter);
|
|
4223
4255
|
setHighlightedIndex(0);
|
|
4224
4256
|
return;
|
|
4225
4257
|
}
|
|
4226
4258
|
if (input === "m") {
|
|
4227
|
-
|
|
4259
|
+
const newFilter = assigneeFilter === "me" ? "all" : "me";
|
|
4260
|
+
setAssigneeFilter(newFilter);
|
|
4261
|
+
doFetch(searchText, newFilter);
|
|
4228
4262
|
setHighlightedIndex(0);
|
|
4229
4263
|
return;
|
|
4230
4264
|
}
|
|
4231
4265
|
if (input === "x") {
|
|
4232
4266
|
setAssigneeFilter("all");
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
doFetch("");
|
|
4237
|
-
}
|
|
4267
|
+
setSearchText("");
|
|
4268
|
+
setInputText("");
|
|
4269
|
+
doFetch("", "all");
|
|
4238
4270
|
setHighlightedIndex(0);
|
|
4239
4271
|
return;
|
|
4240
4272
|
}
|
|
4241
4273
|
if (input === "l" && hasMore) {
|
|
4242
|
-
doFetch(searchText, issues.length, true);
|
|
4274
|
+
doFetch(searchText, assigneeFilter, issues.length, true);
|
|
4243
4275
|
return;
|
|
4244
4276
|
}
|
|
4245
4277
|
if (input === "r") {
|
|
4246
|
-
doFetch(searchText);
|
|
4278
|
+
doFetch(searchText, assigneeFilter);
|
|
4247
4279
|
}
|
|
4248
4280
|
},
|
|
4249
4281
|
{ isActive }
|
|
@@ -4260,6 +4292,7 @@ function JiraSavedViewBrowserBox({
|
|
|
4260
4292
|
auth,
|
|
4261
4293
|
myAccountId,
|
|
4262
4294
|
myDisplayName,
|
|
4295
|
+
onFetchCurrentUser,
|
|
4263
4296
|
isActive,
|
|
4264
4297
|
onClose: () => setDetailIssue(null),
|
|
4265
4298
|
onIssueUpdated: handleIssueUpdated,
|
|
@@ -4276,7 +4309,7 @@ function JiraSavedViewBrowserBox({
|
|
|
4276
4309
|
/* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
4277
4310
|
" ",
|
|
4278
4311
|
"(",
|
|
4279
|
-
|
|
4312
|
+
issues.length,
|
|
4280
4313
|
"/",
|
|
4281
4314
|
total,
|
|
4282
4315
|
")"
|
|
@@ -4286,12 +4319,11 @@ function JiraSavedViewBrowserBox({
|
|
|
4286
4319
|
/* @__PURE__ */ jsxs10(Box10, { flexGrow: 1, flexBasis: 0, overflow: "hidden", children: [
|
|
4287
4320
|
!view && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "Select a view to browse issues" }) }),
|
|
4288
4321
|
view && loading && issues.length === 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
|
|
4289
|
-
/* @__PURE__ */ jsx12(
|
|
4322
|
+
/* @__PURE__ */ jsx12(Spinner3, { type: "dots" }),
|
|
4290
4323
|
" Loading issues..."
|
|
4291
4324
|
] }) }),
|
|
4292
4325
|
view && error && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { color: "red", children: error }) }),
|
|
4293
|
-
view && !loading && !error && issues.length === 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children:
|
|
4294
|
-
view && !loading && !error && filteredIssues.length === 0 && issues.length > 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "No issues match filter" }) }),
|
|
4326
|
+
view && !loading && !error && issues.length === 0 && /* @__PURE__ */ jsx12(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: hasActiveFilters ? "No issues match filter" : "No issues found" }) }),
|
|
4295
4327
|
rows.length > 0 && /* @__PURE__ */ jsx12(ScrollView6, { ref: scrollRef, children: rows.map((row, rowIdx) => {
|
|
4296
4328
|
if (row.type === "header") {
|
|
4297
4329
|
const stateLabel = row.state === "active" ? " (active)" : "";
|
|
@@ -4456,6 +4488,7 @@ function JiraBrowserView({
|
|
|
4456
4488
|
const [myDisplayName, setMyDisplayName] = useState17(null);
|
|
4457
4489
|
const [inputModeActive, setInputModeActive] = useState17(false);
|
|
4458
4490
|
const lastRepoRef = useRef7(null);
|
|
4491
|
+
const fetchingUserRef = useRef7(false);
|
|
4459
4492
|
const auth = useMemo3(() => {
|
|
4460
4493
|
if (!repo.repoPath || !isJiraConfigured(repo.repoPath)) return null;
|
|
4461
4494
|
const siteUrl = getJiraSiteUrl(repo.repoPath);
|
|
@@ -4473,18 +4506,21 @@ function JiraBrowserView({
|
|
|
4473
4506
|
setSelectedViewId(loaded[0].id);
|
|
4474
4507
|
}
|
|
4475
4508
|
}, [repo.repoPath]);
|
|
4476
|
-
|
|
4477
|
-
if (
|
|
4478
|
-
|
|
4479
|
-
return;
|
|
4509
|
+
const fetchCurrentUser = useCallback10(async () => {
|
|
4510
|
+
if (myAccountId && myDisplayName) {
|
|
4511
|
+
return { accountId: myAccountId, displayName: myDisplayName };
|
|
4480
4512
|
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4513
|
+
if (!auth || fetchingUserRef.current) return null;
|
|
4514
|
+
fetchingUserRef.current = true;
|
|
4515
|
+
const result = await getCurrentUser(auth);
|
|
4516
|
+
fetchingUserRef.current = false;
|
|
4517
|
+
if (result.success) {
|
|
4518
|
+
setMyAccountId(result.data.accountId);
|
|
4519
|
+
setMyDisplayName(result.data.displayName);
|
|
4520
|
+
return { accountId: result.data.accountId, displayName: result.data.displayName };
|
|
4521
|
+
}
|
|
4522
|
+
return null;
|
|
4523
|
+
}, [auth, myAccountId, myDisplayName]);
|
|
4488
4524
|
useEffect13(() => {
|
|
4489
4525
|
onModalChange == null ? void 0 : onModalChange(modal.isOpen || inputModeActive);
|
|
4490
4526
|
}, [modal.isOpen, inputModeActive, onModalChange]);
|
|
@@ -4547,7 +4583,7 @@ function JiraBrowserView({
|
|
|
4547
4583
|
if (input === "5") onFocusedBoxChange("saved-views");
|
|
4548
4584
|
if (input === "6") onFocusedBoxChange("browser");
|
|
4549
4585
|
},
|
|
4550
|
-
{ isActive: isActive && !modal.isOpen }
|
|
4586
|
+
{ isActive: isActive && !modal.isOpen && !inputModeActive }
|
|
4551
4587
|
);
|
|
4552
4588
|
if (modal.type === "add") {
|
|
4553
4589
|
return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx14(
|
|
@@ -4586,6 +4622,7 @@ function JiraBrowserView({
|
|
|
4586
4622
|
auth,
|
|
4587
4623
|
myAccountId,
|
|
4588
4624
|
myDisplayName,
|
|
4625
|
+
onFetchCurrentUser: fetchCurrentUser,
|
|
4589
4626
|
isActive: isActive && focusedBox === "browser",
|
|
4590
4627
|
onInputModeChange: setInputModeActive,
|
|
4591
4628
|
onLogUpdated
|
|
@@ -5337,9 +5374,371 @@ function LogsView({ isActive, refreshKey, focusedBox, onFocusedBoxChange }) {
|
|
|
5337
5374
|
] });
|
|
5338
5375
|
}
|
|
5339
5376
|
|
|
5377
|
+
// src/components/pull-requests/AllPullRequestsView.tsx
|
|
5378
|
+
import open6 from "open";
|
|
5379
|
+
import { useCallback as useCallback11, useEffect as useEffect18, useState as useState22 } from "react";
|
|
5380
|
+
import { TitledBox as TitledBox9 } from "@mishieck/ink-titled-box";
|
|
5381
|
+
import { Box as Box21, Text as Text20, useInput as useInput18 } from "ink";
|
|
5382
|
+
import { ScrollView as ScrollView11 } from "ink-scroll-view";
|
|
5383
|
+
import Spinner4 from "ink-spinner";
|
|
5384
|
+
|
|
5385
|
+
// src/lib/github/pr-list.ts
|
|
5386
|
+
import { exec as exec4 } from "child_process";
|
|
5387
|
+
import { promisify as promisify2 } from "util";
|
|
5388
|
+
var execAsync2 = promisify2(exec4);
|
|
5389
|
+
async function listPRs(repo, opts) {
|
|
5390
|
+
if (!await isGhInstalled()) {
|
|
5391
|
+
return {
|
|
5392
|
+
success: false,
|
|
5393
|
+
error: "GitHub CLI (gh) is not installed. Install from https://cli.github.com",
|
|
5394
|
+
errorType: "not_installed"
|
|
5395
|
+
};
|
|
5396
|
+
}
|
|
5397
|
+
if (!await isGhAuthenticated()) {
|
|
5398
|
+
return {
|
|
5399
|
+
success: false,
|
|
5400
|
+
error: "Not authenticated. Run 'gh auth login' to authenticate.",
|
|
5401
|
+
errorType: "not_authenticated"
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
const fields = "number,title,state,author,createdAt,isDraft,reviewDecision,statusCheckRollup";
|
|
5405
|
+
const state = (opts == null ? void 0 : opts.state) ?? "open";
|
|
5406
|
+
const limit = (opts == null ? void 0 : opts.limit) ?? 30;
|
|
5407
|
+
const args = [`gh pr list`, `--state ${state}`, `--limit ${limit}`, `--json ${fields}`, `--repo "${repo}"`];
|
|
5408
|
+
if (opts == null ? void 0 : opts.search) {
|
|
5409
|
+
args.push(`--search "${opts.search.replace(/"/g, '\\"')}"`);
|
|
5410
|
+
}
|
|
5411
|
+
try {
|
|
5412
|
+
const { stdout } = await execAsync2(`${args.join(" ")} 2>/dev/null`);
|
|
5413
|
+
const prs = JSON.parse(stdout);
|
|
5414
|
+
return { success: true, data: prs };
|
|
5415
|
+
} catch {
|
|
5416
|
+
return { success: false, error: "Failed to fetch PRs", errorType: "api_error" };
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
async function checkoutPR(prNumber, repo) {
|
|
5420
|
+
if (!await isGhInstalled()) {
|
|
5421
|
+
return { success: false, error: "gh CLI not installed", errorType: "not_installed" };
|
|
5422
|
+
}
|
|
5423
|
+
if (!await isGhAuthenticated()) {
|
|
5424
|
+
return { success: false, error: "gh CLI not authenticated", errorType: "not_authenticated" };
|
|
5425
|
+
}
|
|
5426
|
+
try {
|
|
5427
|
+
await execAsync2(`gh pr checkout ${prNumber} --repo "${repo}" 2>&1`);
|
|
5428
|
+
return { success: true, data: `Checked out PR #${prNumber}` };
|
|
5429
|
+
} catch (e) {
|
|
5430
|
+
const msg = e instanceof Error ? e.message.split("\n").pop() ?? e.message : "Failed to checkout PR";
|
|
5431
|
+
return { success: false, error: msg, errorType: "api_error" };
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
// src/components/pull-requests/AllPullRequestsView.tsx
|
|
5436
|
+
import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
5437
|
+
var STATE_CYCLE = ["open", "closed", "all"];
|
|
5438
|
+
function computeOverallCheck(checks) {
|
|
5439
|
+
if (!checks || checks.length === 0) return null;
|
|
5440
|
+
const statuses = checks.map(resolveCheckStatus);
|
|
5441
|
+
if (statuses.some((s) => s === "failure")) return "failure";
|
|
5442
|
+
if (statuses.some((s) => s === "pending")) return "pending";
|
|
5443
|
+
return "success";
|
|
5444
|
+
}
|
|
5445
|
+
function AllPullRequestsView({ isActive, onModalChange }) {
|
|
5446
|
+
const repo = useGitRepo();
|
|
5447
|
+
const [prs, setPrs] = useState22([]);
|
|
5448
|
+
const [loading, setLoading] = useState22(false);
|
|
5449
|
+
const [error, setError] = useState22(null);
|
|
5450
|
+
const [detailPR, setDetailPR] = useState22(null);
|
|
5451
|
+
const [prDetails, setPrDetails] = useState22(null);
|
|
5452
|
+
const [detailsLoading, setDetailsLoading] = useState22(false);
|
|
5453
|
+
const [detailsError, setDetailsError] = useState22(void 0);
|
|
5454
|
+
const [stateFilter, setStateFilter] = useState22("open");
|
|
5455
|
+
const [searchText, setSearchText] = useState22("");
|
|
5456
|
+
const [inputText, setInputText] = useState22("");
|
|
5457
|
+
const [limit, setLimit] = useState22(30);
|
|
5458
|
+
const [isSearching, setIsSearching] = useState22(false);
|
|
5459
|
+
const [checkoutLoading, setCheckoutLoading] = useState22(false);
|
|
5460
|
+
const [checkoutResult, setCheckoutResult] = useState22(null);
|
|
5461
|
+
useEffect18(() => {
|
|
5462
|
+
onModalChange == null ? void 0 : onModalChange(isSearching || detailPR !== null);
|
|
5463
|
+
}, [isSearching, detailPR, onModalChange]);
|
|
5464
|
+
const doFetch = useCallback11(
|
|
5465
|
+
async (state, search, fetchLimit = 30) => {
|
|
5466
|
+
if (!repo.currentRepoSlug) return;
|
|
5467
|
+
setLoading(true);
|
|
5468
|
+
setError(null);
|
|
5469
|
+
setPrs([]);
|
|
5470
|
+
const result = await listPRs(repo.currentRepoSlug, {
|
|
5471
|
+
state,
|
|
5472
|
+
search: search || void 0,
|
|
5473
|
+
limit: fetchLimit
|
|
5474
|
+
});
|
|
5475
|
+
if (result.success) {
|
|
5476
|
+
setPrs(result.data);
|
|
5477
|
+
} else {
|
|
5478
|
+
setError(result.error);
|
|
5479
|
+
}
|
|
5480
|
+
setLoading(false);
|
|
5481
|
+
},
|
|
5482
|
+
[repo.currentRepoSlug]
|
|
5483
|
+
);
|
|
5484
|
+
useEffect18(() => {
|
|
5485
|
+
if (repo.currentRepoSlug) {
|
|
5486
|
+
setStateFilter("open");
|
|
5487
|
+
setSearchText("");
|
|
5488
|
+
setInputText("");
|
|
5489
|
+
doFetch("open", "");
|
|
5490
|
+
}
|
|
5491
|
+
}, [repo.currentRepoSlug]);
|
|
5492
|
+
const fetchDetails = useCallback11(
|
|
5493
|
+
async (pr) => {
|
|
5494
|
+
if (!repo.currentRepoSlug) return;
|
|
5495
|
+
setDetailsLoading(true);
|
|
5496
|
+
setDetailsError(void 0);
|
|
5497
|
+
const result = await getPRDetails(pr.number, repo.currentRepoSlug);
|
|
5498
|
+
if (result.success) {
|
|
5499
|
+
setPrDetails(result.data);
|
|
5500
|
+
setDetailsError(void 0);
|
|
5501
|
+
} else {
|
|
5502
|
+
setDetailsError(result.error);
|
|
5503
|
+
}
|
|
5504
|
+
setDetailsLoading(false);
|
|
5505
|
+
},
|
|
5506
|
+
[repo.currentRepoSlug]
|
|
5507
|
+
);
|
|
5508
|
+
const handleSelect = useCallback11(
|
|
5509
|
+
(index) => {
|
|
5510
|
+
const pr = prs[index];
|
|
5511
|
+
if (pr) {
|
|
5512
|
+
setDetailPR(pr);
|
|
5513
|
+
fetchDetails(pr);
|
|
5514
|
+
}
|
|
5515
|
+
},
|
|
5516
|
+
[prs, fetchDetails]
|
|
5517
|
+
);
|
|
5518
|
+
const { highlightedIndex, scrollRef } = useListNavigation({
|
|
5519
|
+
items: prs,
|
|
5520
|
+
selectedIndex: detailPR ? prs.findIndex((p) => p.number === detailPR.number) : -1,
|
|
5521
|
+
onSelect: handleSelect,
|
|
5522
|
+
isActive: isActive && !detailPR && !isSearching
|
|
5523
|
+
});
|
|
5524
|
+
const currentPR = prs[highlightedIndex] ?? null;
|
|
5525
|
+
const getPRUrl = (pr) => {
|
|
5526
|
+
if (!repo.currentRepoSlug) return null;
|
|
5527
|
+
return `https://github.com/${repo.currentRepoSlug}/pull/${pr.number}`;
|
|
5528
|
+
};
|
|
5529
|
+
const hasActiveFilters = stateFilter !== "open" || searchText.length > 0;
|
|
5530
|
+
useInput18(
|
|
5531
|
+
(input, key) => {
|
|
5532
|
+
if (isSearching) {
|
|
5533
|
+
if (key.escape) {
|
|
5534
|
+
setIsSearching(false);
|
|
5535
|
+
setInputText(searchText);
|
|
5536
|
+
return;
|
|
5537
|
+
}
|
|
5538
|
+
if (key.return) {
|
|
5539
|
+
setIsSearching(false);
|
|
5540
|
+
const newSearch = inputText.trim();
|
|
5541
|
+
if (newSearch !== searchText) {
|
|
5542
|
+
setSearchText(newSearch);
|
|
5543
|
+
setLimit(30);
|
|
5544
|
+
doFetch(stateFilter, newSearch);
|
|
5545
|
+
}
|
|
5546
|
+
return;
|
|
5547
|
+
}
|
|
5548
|
+
if (key.backspace || key.delete) {
|
|
5549
|
+
setInputText((prev) => prev.slice(0, -1));
|
|
5550
|
+
return;
|
|
5551
|
+
}
|
|
5552
|
+
if (input && input.length > 0) {
|
|
5553
|
+
const printable = input.replace(/[^\x20-\x7E\u00A0-\uFFFF]/g, "");
|
|
5554
|
+
if (printable.length > 0) {
|
|
5555
|
+
setInputText((prev) => prev + printable);
|
|
5556
|
+
}
|
|
5557
|
+
return;
|
|
5558
|
+
}
|
|
5559
|
+
return;
|
|
5560
|
+
}
|
|
5561
|
+
if (input === "/") {
|
|
5562
|
+
setIsSearching(true);
|
|
5563
|
+
setInputText(searchText);
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5566
|
+
if (input === "s") {
|
|
5567
|
+
const idx = STATE_CYCLE.indexOf(stateFilter);
|
|
5568
|
+
const newState = STATE_CYCLE[(idx + 1) % STATE_CYCLE.length];
|
|
5569
|
+
setStateFilter(newState);
|
|
5570
|
+
setLimit(30);
|
|
5571
|
+
doFetch(newState, searchText);
|
|
5572
|
+
return;
|
|
5573
|
+
}
|
|
5574
|
+
if (input === "x") {
|
|
5575
|
+
setStateFilter("open");
|
|
5576
|
+
setSearchText("");
|
|
5577
|
+
setInputText("");
|
|
5578
|
+
setLimit(30);
|
|
5579
|
+
doFetch("open", "");
|
|
5580
|
+
return;
|
|
5581
|
+
}
|
|
5582
|
+
if (input === "o" && currentPR) {
|
|
5583
|
+
const url = getPRUrl(currentPR);
|
|
5584
|
+
if (url) open6(url).catch(() => {
|
|
5585
|
+
});
|
|
5586
|
+
}
|
|
5587
|
+
if (input === "y" && currentPR) {
|
|
5588
|
+
const url = getPRUrl(currentPR);
|
|
5589
|
+
if (url) copyToClipboard(url);
|
|
5590
|
+
}
|
|
5591
|
+
if (input === "l") {
|
|
5592
|
+
const newLimit = limit + 30;
|
|
5593
|
+
setLimit(newLimit);
|
|
5594
|
+
doFetch(stateFilter, searchText, newLimit);
|
|
5595
|
+
return;
|
|
5596
|
+
}
|
|
5597
|
+
if (input === "r") {
|
|
5598
|
+
doFetch(stateFilter, searchText, limit);
|
|
5599
|
+
}
|
|
5600
|
+
},
|
|
5601
|
+
{ isActive: isActive && !detailPR }
|
|
5602
|
+
);
|
|
5603
|
+
useInput18(
|
|
5604
|
+
(input, key) => {
|
|
5605
|
+
if (key.escape) {
|
|
5606
|
+
setDetailPR(null);
|
|
5607
|
+
setCheckoutResult(null);
|
|
5608
|
+
return;
|
|
5609
|
+
}
|
|
5610
|
+
if (input === "y" && detailPR) {
|
|
5611
|
+
const url = getPRUrl(detailPR);
|
|
5612
|
+
if (url) copyToClipboard(url);
|
|
5613
|
+
}
|
|
5614
|
+
if (input === "c" && detailPR && !checkoutLoading && repo.currentRepoSlug) {
|
|
5615
|
+
setCheckoutLoading(true);
|
|
5616
|
+
setCheckoutResult(null);
|
|
5617
|
+
checkoutPR(detailPR.number, repo.currentRepoSlug).then((result) => {
|
|
5618
|
+
setCheckoutLoading(false);
|
|
5619
|
+
if (result.success) {
|
|
5620
|
+
setCheckoutResult({ success: true, message: `Checked out #${detailPR.number}` });
|
|
5621
|
+
repo.refreshBranch();
|
|
5622
|
+
} else {
|
|
5623
|
+
setCheckoutResult({ success: false, message: result.error });
|
|
5624
|
+
}
|
|
5625
|
+
});
|
|
5626
|
+
}
|
|
5627
|
+
},
|
|
5628
|
+
{ isActive: isActive && detailPR !== null }
|
|
5629
|
+
);
|
|
5630
|
+
const filterParts = [];
|
|
5631
|
+
if (stateFilter !== "open") filterParts.push(stateFilter);
|
|
5632
|
+
if (searchText) filterParts.push(`"${searchText}"`);
|
|
5633
|
+
const borderColor = isActive ? "yellow" : void 0;
|
|
5634
|
+
const stateColor = (pr) => {
|
|
5635
|
+
const display = resolveMergeDisplay({
|
|
5636
|
+
state: pr.state,
|
|
5637
|
+
isDraft: pr.isDraft,
|
|
5638
|
+
mergeable: "UNKNOWN"
|
|
5639
|
+
});
|
|
5640
|
+
return display.color;
|
|
5641
|
+
};
|
|
5642
|
+
const stateLabel = (pr) => {
|
|
5643
|
+
if (pr.isDraft) return "Draft";
|
|
5644
|
+
if (pr.state === "MERGED") return "Merged";
|
|
5645
|
+
if (pr.state === "CLOSED") return "Closed";
|
|
5646
|
+
return "Open";
|
|
5647
|
+
};
|
|
5648
|
+
if (detailPR) {
|
|
5649
|
+
return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", flexGrow: 1, children: [
|
|
5650
|
+
/* @__PURE__ */ jsx23(
|
|
5651
|
+
PRDetailsBox,
|
|
5652
|
+
{
|
|
5653
|
+
pr: prDetails,
|
|
5654
|
+
loading: detailsLoading,
|
|
5655
|
+
error: detailsError,
|
|
5656
|
+
isActive,
|
|
5657
|
+
title: "[5] Pull Requests"
|
|
5658
|
+
}
|
|
5659
|
+
),
|
|
5660
|
+
checkoutLoading ? /* @__PURE__ */ jsxs21(Text20, { color: "yellow", children: [
|
|
5661
|
+
" ",
|
|
5662
|
+
/* @__PURE__ */ jsx23(Spinner4, { type: "dots" }),
|
|
5663
|
+
" Checking out..."
|
|
5664
|
+
] }) : checkoutResult ? /* @__PURE__ */ jsxs21(Text20, { color: checkoutResult.success ? "green" : "red", children: [
|
|
5665
|
+
" ",
|
|
5666
|
+
checkoutResult.message
|
|
5667
|
+
] }) : /* @__PURE__ */ jsx23(Text20, { dimColor: true, children: " Esc back \xB7 j/k scroll \xB7 o open \xB7 y copy \xB7 c checkout" })
|
|
5668
|
+
] });
|
|
5669
|
+
}
|
|
5670
|
+
return /* @__PURE__ */ jsx23(Box21, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx23(TitledBox9, { borderStyle: "round", titles: ["[5] Pull Requests"], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
|
|
5671
|
+
(isSearching || hasActiveFilters) && /* @__PURE__ */ jsxs21(Box21, { children: [
|
|
5672
|
+
/* @__PURE__ */ jsx23(Text20, { color: "blue", children: "Filter: " }),
|
|
5673
|
+
isSearching ? /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
5674
|
+
/* @__PURE__ */ jsx23(Text20, { children: inputText }),
|
|
5675
|
+
/* @__PURE__ */ jsx23(Text20, { backgroundColor: "yellow", children: " " })
|
|
5676
|
+
] }) : /* @__PURE__ */ jsxs21(Fragment5, { children: [
|
|
5677
|
+
/* @__PURE__ */ jsx23(Text20, { children: filterParts.join(" + ") }),
|
|
5678
|
+
/* @__PURE__ */ jsxs21(Text20, { dimColor: true, children: [
|
|
5679
|
+
" (",
|
|
5680
|
+
prs.length,
|
|
5681
|
+
")"
|
|
5682
|
+
] })
|
|
5683
|
+
] })
|
|
5684
|
+
] }),
|
|
5685
|
+
loading && /* @__PURE__ */ jsxs21(Text20, { color: "yellow", children: [
|
|
5686
|
+
/* @__PURE__ */ jsx23(Spinner4, { type: "dots" }),
|
|
5687
|
+
" Loading PRs..."
|
|
5688
|
+
] }),
|
|
5689
|
+
error && /* @__PURE__ */ jsx23(Text20, { color: "red", children: error }),
|
|
5690
|
+
!loading && !error && prs.length === 0 && /* @__PURE__ */ jsx23(Text20, { dimColor: true, children: hasActiveFilters ? "No PRs match filter" : "No open PRs" }),
|
|
5691
|
+
!loading && !error && prs.length > 0 && /* @__PURE__ */ jsx23(ScrollView11, { ref: scrollRef, children: prs.map((pr, idx) => {
|
|
5692
|
+
const isHighlighted = isActive && idx === highlightedIndex;
|
|
5693
|
+
const cursor = isHighlighted ? ">" : " ";
|
|
5694
|
+
const review = resolveReviewDisplay(pr.reviewDecision);
|
|
5695
|
+
const overallCheck = computeOverallCheck(pr.statusCheckRollup);
|
|
5696
|
+
return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
|
|
5697
|
+
/* @__PURE__ */ jsxs21(Box21, { children: [
|
|
5698
|
+
/* @__PURE__ */ jsxs21(Text20, { color: isHighlighted ? "yellow" : void 0, children: [
|
|
5699
|
+
cursor,
|
|
5700
|
+
" "
|
|
5701
|
+
] }),
|
|
5702
|
+
/* @__PURE__ */ jsxs21(Text20, { children: [
|
|
5703
|
+
"#",
|
|
5704
|
+
pr.number
|
|
5705
|
+
] }),
|
|
5706
|
+
/* @__PURE__ */ jsxs21(Text20, { children: [
|
|
5707
|
+
" ",
|
|
5708
|
+
pr.title,
|
|
5709
|
+
" "
|
|
5710
|
+
] }),
|
|
5711
|
+
(pr.state !== "OPEN" || pr.isDraft) && /* @__PURE__ */ jsxs21(Text20, { color: stateColor(pr), children: [
|
|
5712
|
+
"[",
|
|
5713
|
+
stateLabel(pr),
|
|
5714
|
+
"]"
|
|
5715
|
+
] })
|
|
5716
|
+
] }),
|
|
5717
|
+
/* @__PURE__ */ jsxs21(Box21, { children: [
|
|
5718
|
+
/* @__PURE__ */ jsx23(Text20, { children: " " }),
|
|
5719
|
+
/* @__PURE__ */ jsxs21(Text20, { dimColor: true, children: [
|
|
5720
|
+
" ",
|
|
5721
|
+
pr.author.login,
|
|
5722
|
+
" \xB7 ",
|
|
5723
|
+
timeAgo(pr.createdAt)
|
|
5724
|
+
] }),
|
|
5725
|
+
pr.reviewDecision && /* @__PURE__ */ jsxs21(Text20, { dimColor: true, children: [
|
|
5726
|
+
" \xB7 ",
|
|
5727
|
+
review.text
|
|
5728
|
+
] }),
|
|
5729
|
+
overallCheck && /* @__PURE__ */ jsxs21(Text20, { color: CHECK_COLORS[overallCheck], children: [
|
|
5730
|
+
" ",
|
|
5731
|
+
CHECK_ICONS[overallCheck]
|
|
5732
|
+
] })
|
|
5733
|
+
] })
|
|
5734
|
+
] }, pr.number);
|
|
5735
|
+
}) })
|
|
5736
|
+
] }) }) });
|
|
5737
|
+
}
|
|
5738
|
+
|
|
5340
5739
|
// src/components/ui/KeybindingsBar.tsx
|
|
5341
|
-
import { Box as
|
|
5342
|
-
import { jsx as
|
|
5740
|
+
import { Box as Box22, Text as Text21 } from "ink";
|
|
5741
|
+
import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
5343
5742
|
var globalBindings = [
|
|
5344
5743
|
{ key: "1-4", label: "Focus" },
|
|
5345
5744
|
{ key: "Tab", label: "Switch Tab" },
|
|
@@ -5350,14 +5749,14 @@ var modalBindings = [{ key: "Esc", label: "Cancel" }];
|
|
|
5350
5749
|
var DUCK_ASCII = "<(')___";
|
|
5351
5750
|
function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
|
|
5352
5751
|
const allBindings = modalOpen ? [...contextBindings, ...modalBindings] : [...contextBindings, ...globalBindings];
|
|
5353
|
-
return /* @__PURE__ */
|
|
5354
|
-
allBindings.map((binding) => /* @__PURE__ */
|
|
5355
|
-
/* @__PURE__ */
|
|
5356
|
-
/* @__PURE__ */
|
|
5752
|
+
return /* @__PURE__ */ jsxs22(Box22, { flexShrink: 0, paddingX: 1, gap: 2, children: [
|
|
5753
|
+
allBindings.map((binding) => /* @__PURE__ */ jsxs22(Box22, { gap: 1, children: [
|
|
5754
|
+
/* @__PURE__ */ jsx24(Text21, { bold: true, color: binding.color ?? "yellow", children: binding.key }),
|
|
5755
|
+
/* @__PURE__ */ jsx24(Text21, { dimColor: true, children: binding.label })
|
|
5357
5756
|
] }, binding.key)),
|
|
5358
|
-
(duck == null ? void 0 : duck.visible) && /* @__PURE__ */
|
|
5359
|
-
/* @__PURE__ */
|
|
5360
|
-
/* @__PURE__ */
|
|
5757
|
+
(duck == null ? void 0 : duck.visible) && /* @__PURE__ */ jsxs22(Box22, { flexGrow: 1, justifyContent: "flex-end", gap: 1, children: [
|
|
5758
|
+
/* @__PURE__ */ jsx24(Text21, { children: DUCK_ASCII }),
|
|
5759
|
+
/* @__PURE__ */ jsx24(Text21, { dimColor: true, children: duck.message })
|
|
5361
5760
|
] })
|
|
5362
5761
|
] });
|
|
5363
5762
|
}
|
|
@@ -5365,7 +5764,8 @@ function KeybindingsBar({ contextBindings = [], modalOpen = false, duck }) {
|
|
|
5365
5764
|
// src/constants/tabs.ts
|
|
5366
5765
|
var COLUMN2_TABS = [
|
|
5367
5766
|
{ id: "logs", label: "Logs" },
|
|
5368
|
-
{ id: "jira-browser", label: "Jira" }
|
|
5767
|
+
{ id: "jira-browser", label: "Jira" },
|
|
5768
|
+
{ id: "pull-requests", label: "PRs" }
|
|
5369
5769
|
];
|
|
5370
5770
|
|
|
5371
5771
|
// src/constants/github.ts
|
|
@@ -5435,6 +5835,18 @@ var LOGS_KEYBINDINGS = {
|
|
|
5435
5835
|
]
|
|
5436
5836
|
};
|
|
5437
5837
|
|
|
5838
|
+
// src/constants/pull-requests.ts
|
|
5839
|
+
var PULL_REQUESTS_KEYBINDINGS = [
|
|
5840
|
+
{ key: "Space", label: "Details" },
|
|
5841
|
+
{ key: "/", label: "Search" },
|
|
5842
|
+
{ key: "s", label: "State" },
|
|
5843
|
+
{ key: "l", label: "Load More" },
|
|
5844
|
+
{ key: "x", label: "Clear Filters" },
|
|
5845
|
+
{ key: "o", label: "Open", color: "green" },
|
|
5846
|
+
{ key: "y", label: "Copy Link" },
|
|
5847
|
+
{ key: "r", label: "Refresh" }
|
|
5848
|
+
];
|
|
5849
|
+
|
|
5438
5850
|
// src/lib/keybindings.ts
|
|
5439
5851
|
function computeKeybindings(focusedView, state) {
|
|
5440
5852
|
switch (focusedView) {
|
|
@@ -5448,39 +5860,53 @@ function computeKeybindings(focusedView, state) {
|
|
|
5448
5860
|
case "jira-browser":
|
|
5449
5861
|
if (state["jira-browser"].modalOpen) return [];
|
|
5450
5862
|
return JIRA_BROWSER_KEYBINDINGS[state["jira-browser"].focusedBox];
|
|
5863
|
+
case "pull-requests":
|
|
5864
|
+
if (state["pull-requests"].modalOpen) return [];
|
|
5865
|
+
return PULL_REQUESTS_KEYBINDINGS;
|
|
5451
5866
|
default:
|
|
5452
5867
|
return [];
|
|
5453
5868
|
}
|
|
5454
5869
|
}
|
|
5455
5870
|
|
|
5456
5871
|
// src/app.tsx
|
|
5457
|
-
import { jsx as
|
|
5872
|
+
import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
5458
5873
|
function App() {
|
|
5459
5874
|
const { exit } = useApp();
|
|
5460
|
-
const [focusedView, setFocusedView] =
|
|
5461
|
-
const [modalOpen, setModalOpen] =
|
|
5462
|
-
const [logRefreshKey, setLogRefreshKey] =
|
|
5463
|
-
const [activeTab, setActiveTab] =
|
|
5875
|
+
const [focusedView, setFocusedView] = useState23("github");
|
|
5876
|
+
const [modalOpen, setModalOpen] = useState23(false);
|
|
5877
|
+
const [logRefreshKey, setLogRefreshKey] = useState23(0);
|
|
5878
|
+
const [activeTab, setActiveTab] = useState23("logs");
|
|
5464
5879
|
const duck = useRubberDuck();
|
|
5465
|
-
const [githubFocusedBox, setGithubFocusedBox] =
|
|
5466
|
-
const [jiraState, setJiraState] =
|
|
5467
|
-
const [logsFocusedBox, setLogsFocusedBox] =
|
|
5468
|
-
const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] =
|
|
5469
|
-
const [jiraBrowserModalOpen, setJiraBrowserModalOpen] =
|
|
5880
|
+
const [githubFocusedBox, setGithubFocusedBox] = useState23("remotes");
|
|
5881
|
+
const [jiraState, setJiraState] = useState23("not_configured");
|
|
5882
|
+
const [logsFocusedBox, setLogsFocusedBox] = useState23("history");
|
|
5883
|
+
const [jiraBrowserFocusedBox, setJiraBrowserFocusedBox] = useState23("saved-views");
|
|
5884
|
+
const [jiraBrowserModalOpen, setJiraBrowserModalOpen] = useState23(false);
|
|
5885
|
+
const [pullRequestsModalOpen, setPullRequestsModalOpen] = useState23(false);
|
|
5470
5886
|
const keybindings = useMemo4(
|
|
5471
5887
|
() => computeKeybindings(focusedView, {
|
|
5472
5888
|
github: { focusedBox: githubFocusedBox },
|
|
5473
5889
|
jira: { jiraState, modalOpen },
|
|
5474
5890
|
logs: { focusedBox: logsFocusedBox },
|
|
5475
|
-
"jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen }
|
|
5891
|
+
"jira-browser": { focusedBox: jiraBrowserFocusedBox, modalOpen: jiraBrowserModalOpen },
|
|
5892
|
+
"pull-requests": { modalOpen: pullRequestsModalOpen }
|
|
5476
5893
|
}),
|
|
5477
|
-
[
|
|
5894
|
+
[
|
|
5895
|
+
focusedView,
|
|
5896
|
+
githubFocusedBox,
|
|
5897
|
+
jiraState,
|
|
5898
|
+
modalOpen,
|
|
5899
|
+
logsFocusedBox,
|
|
5900
|
+
jiraBrowserFocusedBox,
|
|
5901
|
+
jiraBrowserModalOpen,
|
|
5902
|
+
pullRequestsModalOpen
|
|
5903
|
+
]
|
|
5478
5904
|
);
|
|
5479
|
-
const handleLogUpdated =
|
|
5905
|
+
const handleLogUpdated = useCallback12(() => {
|
|
5480
5906
|
setLogRefreshKey((prev) => prev + 1);
|
|
5481
5907
|
}, []);
|
|
5482
|
-
const anyModalOpen = modalOpen || jiraBrowserModalOpen;
|
|
5483
|
-
|
|
5908
|
+
const anyModalOpen = modalOpen || jiraBrowserModalOpen || pullRequestsModalOpen;
|
|
5909
|
+
useInput19(
|
|
5484
5910
|
(input, key) => {
|
|
5485
5911
|
if (key.ctrl && input === "c") {
|
|
5486
5912
|
exit();
|
|
@@ -5519,17 +5945,17 @@ function App() {
|
|
|
5519
5945
|
},
|
|
5520
5946
|
{ isActive: !anyModalOpen }
|
|
5521
5947
|
);
|
|
5522
|
-
return /* @__PURE__ */
|
|
5523
|
-
/* @__PURE__ */
|
|
5524
|
-
/* @__PURE__ */
|
|
5525
|
-
/* @__PURE__ */
|
|
5526
|
-
/* @__PURE__ */
|
|
5527
|
-
COLUMN2_TABS.map((tab) => /* @__PURE__ */
|
|
5948
|
+
return /* @__PURE__ */ jsxs23(Box23, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
|
|
5949
|
+
/* @__PURE__ */ jsxs23(Box23, { height: 1, flexDirection: "row", columnGap: 1, children: [
|
|
5950
|
+
/* @__PURE__ */ jsx25(Box23, { flexGrow: 1, paddingX: 1, flexBasis: 0, children: /* @__PURE__ */ jsx25(Text22, { color: "gray", children: "Current branch" }) }),
|
|
5951
|
+
/* @__PURE__ */ jsxs23(Box23, { flexGrow: 1, gap: 1, flexBasis: 0, children: [
|
|
5952
|
+
/* @__PURE__ */ jsx25(Text22, { color: "gray", children: "Dashboards" }),
|
|
5953
|
+
COLUMN2_TABS.map((tab) => /* @__PURE__ */ jsx25(Text22, { bold: true, dimColor: activeTab !== tab.id, children: tab.label }, tab.id))
|
|
5528
5954
|
] })
|
|
5529
5955
|
] }),
|
|
5530
|
-
/* @__PURE__ */
|
|
5531
|
-
/* @__PURE__ */
|
|
5532
|
-
/* @__PURE__ */
|
|
5956
|
+
/* @__PURE__ */ jsxs23(Box23, { flexGrow: 1, flexDirection: "row", columnGap: 1, children: [
|
|
5957
|
+
/* @__PURE__ */ jsxs23(Box23, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
|
|
5958
|
+
/* @__PURE__ */ jsx25(
|
|
5533
5959
|
GitHubView,
|
|
5534
5960
|
{
|
|
5535
5961
|
isActive: focusedView === "github",
|
|
@@ -5537,7 +5963,7 @@ function App() {
|
|
|
5537
5963
|
onLogUpdated: handleLogUpdated
|
|
5538
5964
|
}
|
|
5539
5965
|
),
|
|
5540
|
-
/* @__PURE__ */
|
|
5966
|
+
/* @__PURE__ */ jsx25(
|
|
5541
5967
|
JiraView,
|
|
5542
5968
|
{
|
|
5543
5969
|
isActive: focusedView === "jira",
|
|
@@ -5547,8 +5973,8 @@ function App() {
|
|
|
5547
5973
|
}
|
|
5548
5974
|
)
|
|
5549
5975
|
] }),
|
|
5550
|
-
/* @__PURE__ */
|
|
5551
|
-
activeTab === "logs" && /* @__PURE__ */
|
|
5976
|
+
/* @__PURE__ */ jsxs23(Box23, { flexDirection: "column", flexGrow: 1, flexBasis: 0, children: [
|
|
5977
|
+
activeTab === "logs" && /* @__PURE__ */ jsx25(
|
|
5552
5978
|
LogsView,
|
|
5553
5979
|
{
|
|
5554
5980
|
isActive: focusedView === "logs",
|
|
@@ -5557,7 +5983,7 @@ function App() {
|
|
|
5557
5983
|
onFocusedBoxChange: setLogsFocusedBox
|
|
5558
5984
|
}
|
|
5559
5985
|
),
|
|
5560
|
-
activeTab === "jira-browser" && /* @__PURE__ */
|
|
5986
|
+
activeTab === "jira-browser" && /* @__PURE__ */ jsx25(
|
|
5561
5987
|
JiraBrowserView,
|
|
5562
5988
|
{
|
|
5563
5989
|
isActive: focusedView === "jira-browser",
|
|
@@ -5566,10 +5992,11 @@ function App() {
|
|
|
5566
5992
|
onModalChange: setJiraBrowserModalOpen,
|
|
5567
5993
|
onLogUpdated: handleLogUpdated
|
|
5568
5994
|
}
|
|
5569
|
-
)
|
|
5995
|
+
),
|
|
5996
|
+
activeTab === "pull-requests" && /* @__PURE__ */ jsx25(AllPullRequestsView, { isActive: focusedView === "pull-requests", onModalChange: setPullRequestsModalOpen })
|
|
5570
5997
|
] })
|
|
5571
5998
|
] }),
|
|
5572
|
-
/* @__PURE__ */
|
|
5999
|
+
/* @__PURE__ */ jsx25(
|
|
5573
6000
|
KeybindingsBar,
|
|
5574
6001
|
{
|
|
5575
6002
|
contextBindings: keybindings,
|
|
@@ -5584,31 +6011,31 @@ function App() {
|
|
|
5584
6011
|
import { render as inkRender } from "ink";
|
|
5585
6012
|
|
|
5586
6013
|
// src/lib/Screen.tsx
|
|
5587
|
-
import { useCallback as
|
|
5588
|
-
import { Box as
|
|
5589
|
-
import { jsx as
|
|
6014
|
+
import { useCallback as useCallback13, useEffect as useEffect19, useState as useState24 } from "react";
|
|
6015
|
+
import { Box as Box24, useStdout as useStdout2 } from "ink";
|
|
6016
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
5590
6017
|
function Screen({ children }) {
|
|
5591
6018
|
const { stdout } = useStdout2();
|
|
5592
|
-
const getSize =
|
|
5593
|
-
const [size, setSize] =
|
|
5594
|
-
|
|
6019
|
+
const getSize = useCallback13(() => ({ height: stdout.rows, width: stdout.columns }), [stdout]);
|
|
6020
|
+
const [size, setSize] = useState24(getSize);
|
|
6021
|
+
useEffect19(() => {
|
|
5595
6022
|
const onResize = () => setSize(getSize());
|
|
5596
6023
|
stdout.on("resize", onResize);
|
|
5597
6024
|
return () => {
|
|
5598
6025
|
stdout.off("resize", onResize);
|
|
5599
6026
|
};
|
|
5600
6027
|
}, [stdout, getSize]);
|
|
5601
|
-
return /* @__PURE__ */
|
|
6028
|
+
return /* @__PURE__ */ jsx26(Box24, { height: size.height, width: size.width, children });
|
|
5602
6029
|
}
|
|
5603
6030
|
|
|
5604
6031
|
// src/lib/render.tsx
|
|
5605
|
-
import { jsx as
|
|
6032
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
5606
6033
|
var ENTER_ALT_BUFFER = "\x1B[?1049h";
|
|
5607
6034
|
var EXIT_ALT_BUFFER = "\x1B[?1049l";
|
|
5608
6035
|
var CLEAR_SCREEN = "\x1B[2J\x1B[H";
|
|
5609
6036
|
function render(node, options) {
|
|
5610
6037
|
process.stdout.write(ENTER_ALT_BUFFER + CLEAR_SCREEN);
|
|
5611
|
-
const element = /* @__PURE__ */
|
|
6038
|
+
const element = /* @__PURE__ */ jsx27(Screen, { children: node });
|
|
5612
6039
|
const instance = inkRender(element, options);
|
|
5613
6040
|
setImmediate(() => instance.rerender(element));
|
|
5614
6041
|
const cleanup = () => process.stdout.write(EXIT_ALT_BUFFER);
|
|
@@ -5629,7 +6056,7 @@ function render(node, options) {
|
|
|
5629
6056
|
}
|
|
5630
6057
|
|
|
5631
6058
|
// src/cli.tsx
|
|
5632
|
-
import { jsx as
|
|
6059
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
5633
6060
|
var cli = meow(
|
|
5634
6061
|
`
|
|
5635
6062
|
Usage
|
|
@@ -5662,4 +6089,4 @@ if (cli.flags.cwd) {
|
|
|
5662
6089
|
process.exit(1);
|
|
5663
6090
|
}
|
|
5664
6091
|
}
|
|
5665
|
-
render(/* @__PURE__ */
|
|
6092
|
+
render(/* @__PURE__ */ jsx28(App, {}));
|