santree 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/pr/create.js +1 -1
- package/dist/lib/dashboard/DetailPanel.js +8 -3
- package/dist/lib/dashboard/IssueList.js +2 -0
- package/dist/lib/dashboard/data.js +68 -0
- package/dist/lib/github.d.ts +4 -2
- package/dist/lib/github.js +19 -6
- package/package.json +1 -1
|
@@ -603,7 +603,7 @@ export default function Dashboard() {
|
|
|
603
603
|
if (!prTemplate) {
|
|
604
604
|
dispatch({
|
|
605
605
|
type: "PR_CREATE_ERROR",
|
|
606
|
-
error: "No PR template found
|
|
606
|
+
error: "No PR template found (checked .github/, docs/, and repo root)",
|
|
607
607
|
});
|
|
608
608
|
return;
|
|
609
609
|
}
|
|
@@ -924,6 +924,10 @@ export default function Dashboard() {
|
|
|
924
924
|
}
|
|
925
925
|
// Open in Linear
|
|
926
926
|
if (input === "o") {
|
|
927
|
+
if (!di.issue.url) {
|
|
928
|
+
dispatch({ type: "SET_ACTION_MESSAGE", message: "No Linear ticket URL" });
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
927
931
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
928
932
|
execSync(`${openCmd} "${di.issue.url}"`, { stdio: "ignore" });
|
|
929
933
|
dispatch({ type: "SET_ACTION_MESSAGE", message: "Opened in browser" });
|
|
@@ -56,7 +56,7 @@ export default function PR({ options }) {
|
|
|
56
56
|
const prTemplate = getPRTemplate();
|
|
57
57
|
if (!prTemplate) {
|
|
58
58
|
setStatus("error");
|
|
59
|
-
setMessage("No PR template found
|
|
59
|
+
setMessage("No PR template found (checked .github/, docs/, and repo root)");
|
|
60
60
|
setTimeout(() => exit(), 100);
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
@@ -8,6 +8,8 @@ function stateColor(type) {
|
|
|
8
8
|
return "blue";
|
|
9
9
|
case "backlog":
|
|
10
10
|
return "gray";
|
|
11
|
+
case "orphaned":
|
|
12
|
+
return "gray";
|
|
11
13
|
default:
|
|
12
14
|
return "yellow";
|
|
13
15
|
}
|
|
@@ -47,7 +49,8 @@ function fileColor(xy) {
|
|
|
47
49
|
return "gray";
|
|
48
50
|
return "yellow";
|
|
49
51
|
}
|
|
50
|
-
function buildActions(
|
|
52
|
+
function buildActions(di) {
|
|
53
|
+
const { worktree, pr, issue } = di;
|
|
51
54
|
const items = [];
|
|
52
55
|
// Work/Resume
|
|
53
56
|
if (worktree?.sessionId) {
|
|
@@ -77,7 +80,9 @@ function buildActions(worktree, pr) {
|
|
|
77
80
|
items.push({ key: "r", label: "Review", color: "cyan" });
|
|
78
81
|
}
|
|
79
82
|
// Links
|
|
80
|
-
|
|
83
|
+
if (issue.url) {
|
|
84
|
+
items.push({ key: "o", label: "Linear", color: "gray" });
|
|
85
|
+
}
|
|
81
86
|
if (pr)
|
|
82
87
|
items.push({ key: "p", label: "Open PR", color: "gray" });
|
|
83
88
|
// Destructive
|
|
@@ -210,7 +215,7 @@ export default function DetailPanel({ issue, scrollOffset, height, width, creati
|
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
// ── Build actions footer ──────────────────────────────────────────
|
|
213
|
-
const actionRows = buildActions(
|
|
218
|
+
const actionRows = buildActions(issue);
|
|
214
219
|
// +1 for the separator line
|
|
215
220
|
const actionsHeight = actionRows.length + 1;
|
|
216
221
|
const scrollableHeight = height - actionsHeight;
|
|
@@ -20,9 +20,13 @@ export async function loadDashboardData(repoRoot) {
|
|
|
20
20
|
}
|
|
21
21
|
// Read metadata once for session IDs
|
|
22
22
|
const metadata = readAllMetadata(repoRoot);
|
|
23
|
+
// Track which ticket IDs are consumed by fetched issues
|
|
24
|
+
const consumedTicketIds = new Set();
|
|
23
25
|
// Enrich issues in parallel
|
|
24
26
|
const enriched = await Promise.all(issues.map(async (issue) => {
|
|
25
27
|
const wt = wtMap.get(issue.identifier);
|
|
28
|
+
if (wt)
|
|
29
|
+
consumedTicketIds.add(issue.identifier);
|
|
26
30
|
let worktreeInfo = null;
|
|
27
31
|
let prInfo = null;
|
|
28
32
|
let checksInfo = null;
|
|
@@ -58,6 +62,56 @@ export async function loadDashboardData(repoRoot) {
|
|
|
58
62
|
reviews: reviewsInfo,
|
|
59
63
|
};
|
|
60
64
|
}));
|
|
65
|
+
// Build orphan DashboardIssue objects for worktrees not matched to any fetched issue
|
|
66
|
+
const orphans = await Promise.all([...wtMap.entries()]
|
|
67
|
+
.filter(([tid]) => !consumedTicketIds.has(tid))
|
|
68
|
+
.map(async ([tid, wt]) => {
|
|
69
|
+
const base = getBaseBranch(wt.branch);
|
|
70
|
+
const [gitStatusOutput, ahead, pr] = await Promise.all([
|
|
71
|
+
getGitStatusAsync(wt.path),
|
|
72
|
+
getCommitsAheadAsync(wt.path, base),
|
|
73
|
+
getPRInfoAsync(wt.branch),
|
|
74
|
+
]);
|
|
75
|
+
let checksInfo = null;
|
|
76
|
+
let reviewsInfo = null;
|
|
77
|
+
if (pr) {
|
|
78
|
+
[checksInfo, reviewsInfo] = await Promise.all([
|
|
79
|
+
getPRChecksAsync(pr.number),
|
|
80
|
+
getPRReviewsAsync(pr.number),
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
// Derive a readable title from branch name: strip prefix and ticket ID
|
|
84
|
+
const titleFromBranch = wt.branch
|
|
85
|
+
.replace(/^[^/]+\//, "") // strip prefix (e.g. "feature/")
|
|
86
|
+
.replace(/^[A-Z]+-\d+-?/, "") // strip ticket ID
|
|
87
|
+
.replace(/-/g, " ")
|
|
88
|
+
.trim() || tid;
|
|
89
|
+
return {
|
|
90
|
+
issue: {
|
|
91
|
+
identifier: tid,
|
|
92
|
+
title: titleFromBranch,
|
|
93
|
+
description: null,
|
|
94
|
+
url: "",
|
|
95
|
+
priority: 0,
|
|
96
|
+
priorityLabel: "None",
|
|
97
|
+
state: { name: "Orphaned", type: "orphaned" },
|
|
98
|
+
labels: [],
|
|
99
|
+
projectId: null,
|
|
100
|
+
projectName: null,
|
|
101
|
+
},
|
|
102
|
+
worktree: {
|
|
103
|
+
path: wt.path,
|
|
104
|
+
branch: wt.branch,
|
|
105
|
+
dirty: Boolean(gitStatusOutput),
|
|
106
|
+
commitsAhead: ahead,
|
|
107
|
+
sessionId: metadata[tid]?.session_id ?? null,
|
|
108
|
+
gitStatus: gitStatusOutput,
|
|
109
|
+
},
|
|
110
|
+
pr,
|
|
111
|
+
checks: checksInfo,
|
|
112
|
+
reviews: reviewsInfo,
|
|
113
|
+
};
|
|
114
|
+
}));
|
|
61
115
|
// Group by project
|
|
62
116
|
const groupMap = new Map();
|
|
63
117
|
for (const di of enriched) {
|
|
@@ -98,6 +152,20 @@ export async function loadDashboardData(repoRoot) {
|
|
|
98
152
|
statusGroups,
|
|
99
153
|
};
|
|
100
154
|
});
|
|
155
|
+
// Append orphaned worktrees as a separate group at the bottom
|
|
156
|
+
if (orphans.length > 0) {
|
|
157
|
+
groups.push({
|
|
158
|
+
name: "Orphaned Worktrees",
|
|
159
|
+
id: null,
|
|
160
|
+
statusGroups: [
|
|
161
|
+
{
|
|
162
|
+
name: "Orphaned",
|
|
163
|
+
type: "orphaned",
|
|
164
|
+
issues: orphans,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
101
169
|
const flatIssues = groups.flatMap((g) => g.statusGroups.flatMap((sg) => sg.issues));
|
|
102
170
|
return { groups, flatIssues };
|
|
103
171
|
}
|
package/dist/lib/github.d.ts
CHANGED
|
@@ -31,8 +31,10 @@ export declare function pushBranch(branchName: string, force?: boolean): boolean
|
|
|
31
31
|
*/
|
|
32
32
|
export declare function createPR(title: string, baseBranch: string, headBranch: string, bodyFile?: string): number;
|
|
33
33
|
/**
|
|
34
|
-
* Fetch the pull request template from the repo
|
|
35
|
-
*
|
|
34
|
+
* Fetch the pull request template from the repo.
|
|
35
|
+
* Checks all standard locations and casings that GitHub supports:
|
|
36
|
+
* .github/, docs/, and repo root — with both lowercase and uppercase filenames.
|
|
37
|
+
* https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository
|
|
36
38
|
* Returns the decoded template content, or null if none exists.
|
|
37
39
|
*/
|
|
38
40
|
export declare function getPRTemplate(): string | null;
|
package/dist/lib/github.js
CHANGED
|
@@ -71,15 +71,28 @@ export function createPR(title, baseBranch, headBranch, bodyFile) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
|
-
* Fetch the pull request template from the repo
|
|
75
|
-
*
|
|
74
|
+
* Fetch the pull request template from the repo.
|
|
75
|
+
* Checks all standard locations and casings that GitHub supports:
|
|
76
|
+
* .github/, docs/, and repo root — with both lowercase and uppercase filenames.
|
|
77
|
+
* https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository
|
|
76
78
|
* Returns the decoded template content, or null if none exists.
|
|
77
79
|
*/
|
|
78
80
|
export function getPRTemplate() {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const paths = [
|
|
82
|
+
".github/pull_request_template.md",
|
|
83
|
+
".github/PULL_REQUEST_TEMPLATE.md",
|
|
84
|
+
"docs/pull_request_template.md",
|
|
85
|
+
"docs/PULL_REQUEST_TEMPLATE.md",
|
|
86
|
+
"pull_request_template.md",
|
|
87
|
+
"PULL_REQUEST_TEMPLATE.md",
|
|
88
|
+
];
|
|
89
|
+
for (const path of paths) {
|
|
90
|
+
const output = run(`gh api repos/{owner}/{repo}/contents/${path} --jq .content`);
|
|
91
|
+
if (output) {
|
|
92
|
+
return Buffer.from(output, "base64").toString("utf-8");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
83
96
|
}
|
|
84
97
|
/**
|
|
85
98
|
* Fetch CI check results for a pull request (async).
|