merge-steward 0.22.2 → 0.22.4
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 +1 -1
- package/dist/watch/App.js +5 -6
- package/dist/watch/OverviewView.d.ts +2 -5
- package/dist/watch/OverviewView.js +18 -50
- package/dist/watch/ProjectDetailView.d.ts +2 -1
- package/dist/watch/ProjectDetailView.js +3 -3
- package/dist/watch/compact-layout.d.ts +32 -0
- package/dist/watch/compact-layout.js +111 -0
- package/package.json +11 -12
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ The skill wraps `merge-steward pr status --wait` and `review-quill pr status --w
|
|
|
34
34
|
Prerequisites: Node.js 24+, `gh` CLI in `PATH`, `git`.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
37
|
+
pnpm add -g merge-steward
|
|
38
38
|
merge-steward init https://queue.example.com
|
|
39
39
|
merge-steward attach owner/repo --base-branch main
|
|
40
40
|
merge-steward doctor --repo repo
|
package/dist/watch/App.js
CHANGED
|
@@ -7,6 +7,7 @@ import { HelpBar } from "./HelpBar.js";
|
|
|
7
7
|
import { OverviewView } from "./OverviewView.js";
|
|
8
8
|
import { ProjectDetailView } from "./ProjectDetailView.js";
|
|
9
9
|
import { StatusBar } from "./StatusBar.js";
|
|
10
|
+
import { computeDashboardLayout } from "./compact-layout.js";
|
|
10
11
|
const REFRESH_INTERVAL_MS = 1_500;
|
|
11
12
|
export function App({ gatewayBaseUrl, repos, initialRepoRef, initialPrNumber }) {
|
|
12
13
|
const { exit } = useApp();
|
|
@@ -152,9 +153,7 @@ export function App({ gatewayBaseUrl, repos, initialRepoRef, initialPrNumber })
|
|
|
152
153
|
setFlashMessage(error instanceof Error ? error.message : String(error));
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
|
-
const
|
|
156
|
-
const chromeRows = 1 /* status */ + 1 /* body marginTop */ + (flashMessage ? 2 : 0) + 2 /* help bar + its marginTop */;
|
|
157
|
-
const bodyRows = Math.max(2, rows - chromeRows);
|
|
156
|
+
const layout = computeDashboardLayout(stdout?.rows ?? 24, Boolean(flashMessage));
|
|
158
157
|
useInput((input, key) => {
|
|
159
158
|
if (input === "q") {
|
|
160
159
|
exit();
|
|
@@ -197,11 +196,11 @@ export function App({ gatewayBaseUrl, repos, initialRepoRef, initialPrNumber })
|
|
|
197
196
|
return;
|
|
198
197
|
}
|
|
199
198
|
if (key.pageDown || input === " ") {
|
|
200
|
-
setDetailScrollOffset((offset) => offset + Math.max(1, bodyRows - 2));
|
|
199
|
+
setDetailScrollOffset((offset) => offset + Math.max(1, layout.bodyRows - 2));
|
|
201
200
|
return;
|
|
202
201
|
}
|
|
203
202
|
if (key.pageUp) {
|
|
204
|
-
setDetailScrollOffset((offset) => Math.max(0, offset - Math.max(1, bodyRows - 2)));
|
|
203
|
+
setDetailScrollOffset((offset) => Math.max(0, offset - Math.max(1, layout.bodyRows - 2)));
|
|
205
204
|
return;
|
|
206
205
|
}
|
|
207
206
|
if (input === "g") {
|
|
@@ -217,5 +216,5 @@ export function App({ gatewayBaseUrl, repos, initialRepoRef, initialPrNumber })
|
|
|
217
216
|
setDetailScrollOffset(0);
|
|
218
217
|
}
|
|
219
218
|
});
|
|
220
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { connected: model.repos.some((repo) => !repo.offlineMessage), lastSnapshotReceivedAt: lastSnapshotReceivedAt, gatewayError: gatewayError }), view === "detail" ? (_jsx(ProjectDetailView, { model: model, selectedRepoId: selectedRepoId, bodyRows: bodyRows, scrollOffset: detailScrollOffset })) : (_jsx(OverviewView, { model: model, selectedRepoId: selectedRepoId, showCursor: true, bodyRows: bodyRows })), flashMessage ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: flashMessage }) })) : null, _jsx(HelpBar, { view: view })] }));
|
|
219
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { connected: model.repos.some((repo) => !repo.offlineMessage), lastSnapshotReceivedAt: lastSnapshotReceivedAt, gatewayError: gatewayError }), view === "detail" ? (_jsx(ProjectDetailView, { model: model, selectedRepoId: selectedRepoId, bodyRows: layout.bodyRows, topMarginRows: layout.bodyTopMarginRows, scrollOffset: detailScrollOffset })) : (_jsx(OverviewView, { model: model, selectedRepoId: selectedRepoId, showCursor: true, bodyRows: layout.bodyRows, topMarginRows: layout.bodyTopMarginRows })), flashMessage && layout.showFlashMessage ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: flashMessage }) })) : null, layout.showHelp ? _jsx(HelpBar, { view: view }) : null] }));
|
|
221
220
|
}
|
|
@@ -4,6 +4,7 @@ interface ListViewProps {
|
|
|
4
4
|
selectedRepoId: string | null;
|
|
5
5
|
showCursor: boolean;
|
|
6
6
|
bodyRows: number;
|
|
7
|
+
topMarginRows?: number | undefined;
|
|
7
8
|
}
|
|
8
9
|
export declare function RepoRow({ repo, selected, showCursor, width }: {
|
|
9
10
|
repo: DashboardRepo;
|
|
@@ -11,9 +12,5 @@ export declare function RepoRow({ repo, selected, showCursor, width }: {
|
|
|
11
12
|
showCursor: boolean;
|
|
12
13
|
width: number;
|
|
13
14
|
}): React.JSX.Element;
|
|
14
|
-
export declare function
|
|
15
|
-
start: number;
|
|
16
|
-
end: number;
|
|
17
|
-
};
|
|
18
|
-
export declare function OverviewView({ model, selectedRepoId, showCursor, bodyRows }: ListViewProps): React.JSX.Element;
|
|
15
|
+
export declare function OverviewView({ model, selectedRepoId, showCursor, bodyRows, topMarginRows }: ListViewProps): React.JSX.Element;
|
|
19
16
|
export {};
|
|
@@ -1,81 +1,49 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useStdout } from "ink";
|
|
3
3
|
import { formatRepoTokenText } from "./format.js";
|
|
4
|
+
import { formatRepoTokensText, pickVisibleParts, } from "./compact-layout.js";
|
|
4
5
|
function RepoTokens({ tokens, width }) {
|
|
5
|
-
|
|
6
|
+
const text = formatRepoTokensText(tokens, width);
|
|
7
|
+
if (!text)
|
|
6
8
|
return null;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
parts.push({ token, text });
|
|
16
|
-
}
|
|
17
|
-
return (_jsx(Text, { children: parts.map((part, index) => (_jsx(Text, { color: part.token.color, children: index === 0 ? part.text : ` ${part.text}` }, `${part.token.prNumber}-${index}`))) }));
|
|
9
|
+
let offset = 0;
|
|
10
|
+
const parts = text.split(" ").map((part) => {
|
|
11
|
+
const token = tokens.find((candidate) => formatRepoTokenText(candidate) === part);
|
|
12
|
+
const item = { text: part, token, offset };
|
|
13
|
+
offset += part.length + 2;
|
|
14
|
+
return item;
|
|
15
|
+
});
|
|
16
|
+
return (_jsx(Text, { children: parts.map((part, index) => (_jsx(Text, { ...(part.token ? { color: part.token.color } : {}), children: index === 0 ? part.text : ` ${part.text}` }, `${part.text}-${part.offset}`))) }));
|
|
18
17
|
}
|
|
19
18
|
export function RepoRow({ repo, selected, showCursor, width, }) {
|
|
20
|
-
const cursorChar = showCursor && selected ? ">" : " ";
|
|
21
19
|
const repoLabelWidth = Math.min(28, Math.max(12, Math.floor(width * 0.35)));
|
|
22
20
|
const tokenWidth = Math.max(6, width - repoLabelWidth - 3);
|
|
23
21
|
const repoLabel = repo.repoFullName.length > repoLabelWidth
|
|
24
22
|
? repo.repoFullName.slice(0, repoLabelWidth)
|
|
25
23
|
: repo.repoFullName.padEnd(repoLabelWidth, " ");
|
|
26
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : "gray", children:
|
|
27
|
-
}
|
|
28
|
-
export function pickVisibleWindow(total, selectedIndex, availableRows) {
|
|
29
|
-
if (total === 0)
|
|
30
|
-
return { start: 0, end: 0 };
|
|
31
|
-
if (total <= availableRows)
|
|
32
|
-
return { start: 0, end: total };
|
|
33
|
-
const clamped = Math.max(0, Math.min(selectedIndex, total - 1));
|
|
34
|
-
let start = clamped;
|
|
35
|
-
let end = clamped + 1;
|
|
36
|
-
while (end - start < availableRows) {
|
|
37
|
-
if (start > 0 && (end === total || clamped - start <= end - 1 - clamped)) {
|
|
38
|
-
start -= 1;
|
|
39
|
-
}
|
|
40
|
-
else if (end < total) {
|
|
41
|
-
end += 1;
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return { start, end };
|
|
24
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : "gray", children: showCursor && selected ? ">" : " " }), _jsx(Text, { bold: selected, children: ` ${repoLabel} ` }), repo.offlineMessage ? (_jsx(Text, { color: "red", children: repo.offlineMessage })) : (_jsx(RepoTokens, { tokens: repo.tokens, width: tokenWidth }))] }));
|
|
48
25
|
}
|
|
49
|
-
export function OverviewView({ model, selectedRepoId, showCursor, bodyRows }) {
|
|
26
|
+
export function OverviewView({ model, selectedRepoId, showCursor, bodyRows, topMarginRows = 1, }) {
|
|
50
27
|
const { stdout } = useStdout();
|
|
51
28
|
const width = Math.max(40, stdout?.columns ?? 80);
|
|
52
29
|
const total = model.repos.length;
|
|
53
30
|
const selectedIndex = Math.max(0, model.repos.findIndex((repo) => repo.repoId === selectedRepoId));
|
|
54
|
-
|
|
55
|
-
let needTop = start > 0;
|
|
56
|
-
let needBottom = end < total;
|
|
57
|
-
if (needTop || needBottom) {
|
|
58
|
-
const reserve = (needTop ? 1 : 0) + (needBottom ? 1 : 0);
|
|
59
|
-
const windowRows = Math.max(1, bodyRows - reserve);
|
|
60
|
-
({ start, end } = pickVisibleWindow(total, selectedIndex, windowRows));
|
|
61
|
-
needTop = start > 0;
|
|
62
|
-
needBottom = end < total;
|
|
63
|
-
}
|
|
31
|
+
const { start, end, showAbove, showBelow } = pickVisibleParts(total, selectedIndex, bodyRows);
|
|
64
32
|
const visible = model.repos.slice(start, end);
|
|
65
33
|
const above = start;
|
|
66
34
|
const below = total - end;
|
|
67
35
|
if (total === 0 || bodyRows <= 0) {
|
|
68
|
-
return _jsx(Box, { marginTop:
|
|
36
|
+
return _jsx(Box, { marginTop: topMarginRows, children: _jsx(Text, { dimColor: true, children: " " }) });
|
|
69
37
|
}
|
|
70
38
|
const children = [];
|
|
71
|
-
if (
|
|
39
|
+
if (showAbove) {
|
|
72
40
|
children.push(_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: `\u2191${above} more above` }) }, "above"));
|
|
73
41
|
}
|
|
74
42
|
for (const repo of visible) {
|
|
75
43
|
children.push(_jsx(RepoRow, { repo: repo, selected: repo.repoId === selectedRepoId, showCursor: showCursor, width: width - 2 }, repo.repoId));
|
|
76
44
|
}
|
|
77
|
-
if (
|
|
45
|
+
if (showBelow) {
|
|
78
46
|
children.push(_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: `\u2193${below} more below` }) }, "below"));
|
|
79
47
|
}
|
|
80
|
-
return (_jsx(Box, { flexDirection: "column", marginTop:
|
|
48
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: topMarginRows, children: children }));
|
|
81
49
|
}
|
|
@@ -3,6 +3,7 @@ interface ProjectDetailViewProps {
|
|
|
3
3
|
model: DashboardModel;
|
|
4
4
|
selectedRepoId: string | null;
|
|
5
5
|
bodyRows: number;
|
|
6
|
+
topMarginRows?: number | undefined;
|
|
6
7
|
scrollOffset: number;
|
|
7
8
|
}
|
|
8
9
|
type ContentLine = {
|
|
@@ -16,5 +17,5 @@ type ContentLine = {
|
|
|
16
17
|
};
|
|
17
18
|
export declare function buildContentLines(repo: DashboardRepo, width: number): ContentLine[];
|
|
18
19
|
export declare function clampScrollOffset(requested: number, totalLines: number, scrollArea: number): number;
|
|
19
|
-
export declare function ProjectDetailView({ model, selectedRepoId, bodyRows, scrollOffset }: ProjectDetailViewProps): React.JSX.Element;
|
|
20
|
+
export declare function ProjectDetailView({ model, selectedRepoId, bodyRows, topMarginRows, scrollOffset }: ProjectDetailViewProps): React.JSX.Element;
|
|
20
21
|
export {};
|
|
@@ -58,14 +58,14 @@ export function clampScrollOffset(requested, totalLines, scrollArea) {
|
|
|
58
58
|
const maxOffset = Math.max(0, totalLines - Math.max(1, scrollArea - 1));
|
|
59
59
|
return Math.max(0, Math.min(requested, maxOffset));
|
|
60
60
|
}
|
|
61
|
-
export function ProjectDetailView({ model, selectedRepoId, bodyRows, scrollOffset }) {
|
|
61
|
+
export function ProjectDetailView({ model, selectedRepoId, bodyRows, topMarginRows = 1, scrollOffset, }) {
|
|
62
62
|
const { stdout } = useStdout();
|
|
63
63
|
const width = Math.max(40, stdout?.columns ?? 80);
|
|
64
64
|
const repo = selectedRepoId
|
|
65
65
|
? model.repos.find((candidate) => candidate.repoId === selectedRepoId) ?? model.repos[0] ?? null
|
|
66
66
|
: model.repos[0] ?? null;
|
|
67
67
|
if (!repo) {
|
|
68
|
-
return _jsx(Box, { marginTop:
|
|
68
|
+
return _jsx(Box, { marginTop: topMarginRows, children: _jsx(Text, { dimColor: true, children: " " }) });
|
|
69
69
|
}
|
|
70
70
|
const repoHeaderRows = 1;
|
|
71
71
|
const separatorRows = bodyRows >= 3 ? 1 : 0;
|
|
@@ -94,5 +94,5 @@ export function ProjectDetailView({ model, selectedRepoId, bodyRows, scrollOffse
|
|
|
94
94
|
const hidden = contentLines.length - tentativeEnd;
|
|
95
95
|
children.push(_jsx(Box, { children: _jsx(Text, { dimColor: true, children: ` \u2193${hidden} more below` }) }, "below"));
|
|
96
96
|
}
|
|
97
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop:
|
|
97
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: topMarginRows, children: [_jsx(RepoRow, { repo: repo, selected: false, showCursor: false, width: width - 2 }), separatorRows > 0 ? _jsx(Box, { children: _jsx(Text, { children: " " }) }) : null, children] }));
|
|
98
98
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DashboardModel, DashboardRepo, DashboardToken } from "./dashboard-model.ts";
|
|
2
|
+
export declare function computeDashboardLayout(totalRows: number, hasFlashMessage: boolean): {
|
|
3
|
+
bodyRows: number;
|
|
4
|
+
bodyTopMarginRows: number;
|
|
5
|
+
showFlashMessage: boolean;
|
|
6
|
+
showHelp: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function pickVisibleWindow(total: number, selectedIndex: number, availableRows: number): {
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function pickVisibleParts(total: number, selectedIndex: number, rowBudget: number): {
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
showAbove: boolean;
|
|
16
|
+
showBelow: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function formatRepoTokensText(tokens: DashboardToken[], width: number): string | null;
|
|
19
|
+
export declare function formatRepoRowText({ repo, selected, showCursor, width }: {
|
|
20
|
+
repo: DashboardRepo;
|
|
21
|
+
selected: boolean;
|
|
22
|
+
showCursor: boolean;
|
|
23
|
+
width: number;
|
|
24
|
+
}): string;
|
|
25
|
+
export declare function renderOverviewLines({ model, selectedRepoId, showCursor, bodyRows, topMarginRows, width }: {
|
|
26
|
+
model: DashboardModel;
|
|
27
|
+
selectedRepoId: string | null;
|
|
28
|
+
showCursor: boolean;
|
|
29
|
+
bodyRows: number;
|
|
30
|
+
topMarginRows?: number | undefined;
|
|
31
|
+
width: number;
|
|
32
|
+
}): string[];
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { formatRepoTokenText } from "./format.js";
|
|
2
|
+
export function computeDashboardLayout(totalRows, hasFlashMessage) {
|
|
3
|
+
const rows = Math.max(1, totalRows);
|
|
4
|
+
const bodyTopMarginRows = rows >= 5 ? 1 : 0;
|
|
5
|
+
const showFlashMessage = hasFlashMessage && rows >= 6;
|
|
6
|
+
const showHelp = rows >= 8;
|
|
7
|
+
const chromeRows = 1
|
|
8
|
+
+ bodyTopMarginRows
|
|
9
|
+
+ (showFlashMessage ? 2 : 0)
|
|
10
|
+
+ (showHelp ? 2 : 0);
|
|
11
|
+
return {
|
|
12
|
+
bodyRows: Math.max(1, rows - chromeRows),
|
|
13
|
+
bodyTopMarginRows,
|
|
14
|
+
showFlashMessage,
|
|
15
|
+
showHelp,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function pickVisibleWindow(total, selectedIndex, availableRows) {
|
|
19
|
+
if (total === 0)
|
|
20
|
+
return { start: 0, end: 0 };
|
|
21
|
+
if (total <= availableRows)
|
|
22
|
+
return { start: 0, end: total };
|
|
23
|
+
const clamped = Math.max(0, Math.min(selectedIndex, total - 1));
|
|
24
|
+
let start = clamped;
|
|
25
|
+
let end = clamped + 1;
|
|
26
|
+
while (end - start < availableRows) {
|
|
27
|
+
if (start > 0 && (end === total || clamped - start <= end - 1 - clamped)) {
|
|
28
|
+
start -= 1;
|
|
29
|
+
}
|
|
30
|
+
else if (end < total) {
|
|
31
|
+
end += 1;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { start, end };
|
|
38
|
+
}
|
|
39
|
+
export function pickVisibleParts(total, selectedIndex, rowBudget) {
|
|
40
|
+
if (total === 0 || rowBudget <= 0) {
|
|
41
|
+
return { start: 0, end: 0, showAbove: false, showBelow: false };
|
|
42
|
+
}
|
|
43
|
+
let { start, end } = pickVisibleWindow(total, selectedIndex, Math.max(1, rowBudget));
|
|
44
|
+
let hiddenAbove = start > 0;
|
|
45
|
+
let hiddenBelow = end < total;
|
|
46
|
+
if (rowBudget >= 3 && (hiddenAbove || hiddenBelow)) {
|
|
47
|
+
const indicatorRows = (hiddenAbove ? 1 : 0) + (hiddenBelow ? 1 : 0);
|
|
48
|
+
({ start, end } = pickVisibleWindow(total, selectedIndex, Math.max(1, rowBudget - indicatorRows)));
|
|
49
|
+
hiddenAbove = start > 0;
|
|
50
|
+
hiddenBelow = end < total;
|
|
51
|
+
}
|
|
52
|
+
const usedRows = end - start;
|
|
53
|
+
let remaining = Math.max(0, rowBudget - usedRows);
|
|
54
|
+
const showAbove = hiddenAbove && remaining > 0;
|
|
55
|
+
if (showAbove)
|
|
56
|
+
remaining -= 1;
|
|
57
|
+
const showBelow = hiddenBelow && remaining > 0;
|
|
58
|
+
return { start, end, showAbove, showBelow };
|
|
59
|
+
}
|
|
60
|
+
export function formatRepoTokensText(tokens, width) {
|
|
61
|
+
if (tokens.length === 0 || width < 6)
|
|
62
|
+
return null;
|
|
63
|
+
const parts = [];
|
|
64
|
+
let used = 0;
|
|
65
|
+
for (const token of tokens) {
|
|
66
|
+
const text = formatRepoTokenText(token);
|
|
67
|
+
const separatorWidth = parts.length === 0 ? 0 : 2;
|
|
68
|
+
if (used + separatorWidth + text.length > width)
|
|
69
|
+
break;
|
|
70
|
+
used += separatorWidth + text.length;
|
|
71
|
+
parts.push(text);
|
|
72
|
+
}
|
|
73
|
+
return parts.join(" ");
|
|
74
|
+
}
|
|
75
|
+
export function formatRepoRowText({ repo, selected, showCursor, width, }) {
|
|
76
|
+
const cursorChar = showCursor && selected ? ">" : " ";
|
|
77
|
+
const repoLabelWidth = Math.min(28, Math.max(12, Math.floor(width * 0.35)));
|
|
78
|
+
const tokenWidth = Math.max(6, width - repoLabelWidth - 3);
|
|
79
|
+
const repoLabel = repo.repoFullName.length > repoLabelWidth
|
|
80
|
+
? repo.repoFullName.slice(0, repoLabelWidth)
|
|
81
|
+
: repo.repoFullName.padEnd(repoLabelWidth, " ");
|
|
82
|
+
return `${cursorChar} ${repoLabel} ${repo.offlineMessage ?? formatRepoTokensText(repo.tokens, tokenWidth) ?? ""}`;
|
|
83
|
+
}
|
|
84
|
+
export function renderOverviewLines({ model, selectedRepoId, showCursor, bodyRows, topMarginRows = 1, width, }) {
|
|
85
|
+
const total = model.repos.length;
|
|
86
|
+
const selectedIndex = Math.max(0, model.repos.findIndex((repo) => repo.repoId === selectedRepoId));
|
|
87
|
+
const lines = Array.from({ length: topMarginRows }, () => "");
|
|
88
|
+
if (total === 0 || bodyRows <= 0) {
|
|
89
|
+
lines.push(" ");
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
const { start, end, showAbove, showBelow } = pickVisibleParts(total, selectedIndex, bodyRows);
|
|
93
|
+
const visible = model.repos.slice(start, end);
|
|
94
|
+
const above = start;
|
|
95
|
+
const below = total - end;
|
|
96
|
+
if (showAbove) {
|
|
97
|
+
lines.push(` \u2191${above} more above`);
|
|
98
|
+
}
|
|
99
|
+
for (const repo of visible) {
|
|
100
|
+
lines.push(formatRepoRowText({
|
|
101
|
+
repo,
|
|
102
|
+
selected: repo.repoId === selectedRepoId,
|
|
103
|
+
showCursor,
|
|
104
|
+
width: width - 2,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
if (showBelow) {
|
|
108
|
+
lines.push(` \u2193${below} more below`);
|
|
109
|
+
}
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "merge-steward",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.4",
|
|
4
4
|
"description": "Serial merge queue for GitHub — rebase, CI-gate, and merge PRs one at a time",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -34,16 +34,6 @@
|
|
|
34
34
|
"bin": {
|
|
35
35
|
"merge-steward": "dist/index.js"
|
|
36
36
|
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"dev": "node --watch --experimental-transform-types src/index.ts",
|
|
39
|
-
"prepack": "pnpm build",
|
|
40
|
-
"build": "rm -rf dist && tsgo -p tsconfig.json && chmod +x dist/index.js",
|
|
41
|
-
"start": "node dist/index.js serve",
|
|
42
|
-
"typecheck": "tsgo -p tsconfig.json --noEmit",
|
|
43
|
-
"test": "node --experimental-transform-types --test 'test/**/*.test.ts'",
|
|
44
|
-
"test:scenarios": "node --experimental-transform-types --test 'test/scenarios/*.test.ts'",
|
|
45
|
-
"test:property": "node --experimental-transform-types --test 'test/property/*.test.ts'"
|
|
46
|
-
},
|
|
47
37
|
"dependencies": {
|
|
48
38
|
"fastify": "^5.8.5",
|
|
49
39
|
"fastify-raw-body": "^5.0.0",
|
|
@@ -59,5 +49,14 @@
|
|
|
59
49
|
"fast-check": "^4.7.0",
|
|
60
50
|
"isomorphic-git": "^1.37.6",
|
|
61
51
|
"memfs": "^4.57.2"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"dev": "node --watch --experimental-transform-types src/index.ts",
|
|
55
|
+
"build": "rm -rf dist && tsgo -p tsconfig.json && chmod +x dist/index.js",
|
|
56
|
+
"start": "node dist/index.js serve",
|
|
57
|
+
"typecheck": "tsgo -p tsconfig.json --noEmit",
|
|
58
|
+
"test": "node --experimental-transform-types --test 'test/**/*.test.ts'",
|
|
59
|
+
"test:scenarios": "node --experimental-transform-types --test 'test/scenarios/*.test.ts'",
|
|
60
|
+
"test:property": "node --experimental-transform-types --test 'test/property/*.test.ts'"
|
|
62
61
|
}
|
|
63
|
-
}
|
|
62
|
+
}
|