@paretools/github 0.8.5 → 0.10.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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/formatters.d.ts +10 -2
- package/dist/lib/formatters.d.ts.map +1 -1
- package/dist/lib/formatters.js +144 -16
- package/dist/lib/formatters.js.map +1 -1
- package/dist/lib/gh-runner.d.ts.map +1 -1
- package/dist/lib/gh-runner.js +2 -1
- package/dist/lib/gh-runner.js.map +1 -1
- package/dist/lib/parsers.d.ts +54 -18
- package/dist/lib/parsers.d.ts.map +1 -1
- package/dist/lib/parsers.js +426 -38
- package/dist/lib/parsers.js.map +1 -1
- package/dist/lib/path-validation.d.ts +13 -0
- package/dist/lib/path-validation.d.ts.map +1 -0
- package/dist/lib/path-validation.js +54 -0
- package/dist/lib/path-validation.js.map +1 -0
- package/dist/schemas/index.d.ts +267 -4
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +195 -4
- package/dist/schemas/index.js.map +1 -1
- package/dist/tools/api.d.ts.map +1 -1
- package/dist/tools/api.js +134 -8
- package/dist/tools/api.js.map +1 -1
- package/dist/tools/gist-create.d.ts.map +1 -1
- package/dist/tools/gist-create.js +91 -21
- package/dist/tools/gist-create.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/issue-close.d.ts.map +1 -1
- package/dist/tools/issue-close.js +56 -9
- package/dist/tools/issue-close.js.map +1 -1
- package/dist/tools/issue-comment.d.ts.map +1 -1
- package/dist/tools/issue-comment.js +60 -9
- package/dist/tools/issue-comment.js.map +1 -1
- package/dist/tools/issue-create.d.ts.map +1 -1
- package/dist/tools/issue-create.js +95 -9
- package/dist/tools/issue-create.js.map +1 -1
- package/dist/tools/issue-list.d.ts.map +1 -1
- package/dist/tools/issue-list.js +139 -11
- package/dist/tools/issue-list.js.map +1 -1
- package/dist/tools/issue-update.d.ts.map +1 -1
- package/dist/tools/issue-update.js +142 -15
- package/dist/tools/issue-update.js.map +1 -1
- package/dist/tools/issue-view.d.ts.map +1 -1
- package/dist/tools/issue-view.js +26 -14
- package/dist/tools/issue-view.js.map +1 -1
- package/dist/tools/label-create.d.ts +4 -0
- package/dist/tools/label-create.d.ts.map +1 -0
- package/dist/tools/label-create.js +73 -0
- package/dist/tools/label-create.js.map +1 -0
- package/dist/tools/label-list.d.ts +4 -0
- package/dist/tools/label-list.d.ts.map +1 -0
- package/dist/tools/label-list.js +71 -0
- package/dist/tools/label-list.js.map +1 -0
- package/dist/tools/pr-checks.d.ts.map +1 -1
- package/dist/tools/pr-checks.js +57 -11
- package/dist/tools/pr-checks.js.map +1 -1
- package/dist/tools/pr-comment.d.ts.map +1 -1
- package/dist/tools/pr-comment.js +60 -9
- package/dist/tools/pr-comment.js.map +1 -1
- package/dist/tools/pr-create.d.ts.map +1 -1
- package/dist/tools/pr-create.js +154 -9
- package/dist/tools/pr-create.js.map +1 -1
- package/dist/tools/pr-diff.d.ts.map +1 -1
- package/dist/tools/pr-diff.js +63 -16
- package/dist/tools/pr-diff.js.map +1 -1
- package/dist/tools/pr-list.d.ts.map +1 -1
- package/dist/tools/pr-list.js +115 -11
- package/dist/tools/pr-list.js.map +1 -1
- package/dist/tools/pr-merge.d.ts.map +1 -1
- package/dist/tools/pr-merge.js +104 -15
- package/dist/tools/pr-merge.js.map +1 -1
- package/dist/tools/pr-review.d.ts.map +1 -1
- package/dist/tools/pr-review.js +41 -8
- package/dist/tools/pr-review.js.map +1 -1
- package/dist/tools/pr-update.d.ts.map +1 -1
- package/dist/tools/pr-update.js +157 -10
- package/dist/tools/pr-update.js.map +1 -1
- package/dist/tools/pr-view.d.ts.map +1 -1
- package/dist/tools/pr-view.js +27 -14
- package/dist/tools/pr-view.js.map +1 -1
- package/dist/tools/release-create.d.ts.map +1 -1
- package/dist/tools/release-create.js +106 -9
- package/dist/tools/release-create.js.map +1 -1
- package/dist/tools/release-list.d.ts.map +1 -1
- package/dist/tools/release-list.js +39 -13
- package/dist/tools/release-list.js.map +1 -1
- package/dist/tools/run-list.d.ts.map +1 -1
- package/dist/tools/run-list.js +111 -12
- package/dist/tools/run-list.js.map +1 -1
- package/dist/tools/run-rerun.d.ts.map +1 -1
- package/dist/tools/run-rerun.js +38 -8
- package/dist/tools/run-rerun.js.map +1 -1
- package/dist/tools/run-view.d.ts.map +1 -1
- package/dist/tools/run-view.js +64 -13
- package/dist/tools/run-view.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../../src/lib/parsers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,cAAc,EACd,UAAU,EACV,cAAc,EACd,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,
|
|
1
|
+
{"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../../src/lib/parsers.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/parsers.js
CHANGED
|
@@ -9,6 +9,20 @@ export function parsePrView(json) {
|
|
|
9
9
|
status: c.status || c.state || "unknown",
|
|
10
10
|
conclusion: c.conclusion ?? null,
|
|
11
11
|
}));
|
|
12
|
+
// P1-gap #147: Parse reviews array
|
|
13
|
+
const reviews = raw.reviews
|
|
14
|
+
? raw.reviews.map((r) => ({
|
|
15
|
+
author: r.author?.login ?? "unknown",
|
|
16
|
+
state: r.state,
|
|
17
|
+
body: r.body || undefined,
|
|
18
|
+
submittedAt: r.submittedAt ?? undefined,
|
|
19
|
+
}))
|
|
20
|
+
: undefined;
|
|
21
|
+
const commits = Array.isArray(raw.commits) ? raw.commits : [];
|
|
22
|
+
const commitCount = commits.length > 0 ? commits.length : undefined;
|
|
23
|
+
const latestCommitSha = commits.length > 0
|
|
24
|
+
? (commits[commits.length - 1]?.oid ?? commits[commits.length - 1]?.commit?.oid ?? undefined)
|
|
25
|
+
: undefined;
|
|
12
26
|
return {
|
|
13
27
|
number: raw.number,
|
|
14
28
|
state: raw.state,
|
|
@@ -23,12 +37,29 @@ export function parsePrView(json) {
|
|
|
23
37
|
additions: raw.additions ?? 0,
|
|
24
38
|
deletions: raw.deletions ?? 0,
|
|
25
39
|
changedFiles: raw.changedFiles ?? 0,
|
|
40
|
+
// S-gap fields
|
|
41
|
+
author: raw.author?.login ?? undefined,
|
|
42
|
+
labels: raw.labels ? raw.labels.map((l) => l.name) : undefined,
|
|
43
|
+
isDraft: raw.isDraft ?? undefined,
|
|
44
|
+
assignees: raw.assignees
|
|
45
|
+
? raw.assignees.map((a) => a.login)
|
|
46
|
+
: undefined,
|
|
47
|
+
createdAt: raw.createdAt ?? undefined,
|
|
48
|
+
updatedAt: raw.updatedAt ?? undefined,
|
|
49
|
+
milestone: raw.milestone?.title ?? undefined,
|
|
50
|
+
projectItems: raw.projectItems
|
|
51
|
+
? raw.projectItems.map((p) => p.title)
|
|
52
|
+
: undefined,
|
|
53
|
+
// P1-gap #147
|
|
54
|
+
reviews,
|
|
55
|
+
commitCount,
|
|
56
|
+
latestCommitSha,
|
|
26
57
|
};
|
|
27
58
|
}
|
|
28
59
|
/**
|
|
29
60
|
* Parses `gh pr list --json ...` output into structured PR list data.
|
|
30
61
|
*/
|
|
31
|
-
export function parsePrList(json) {
|
|
62
|
+
export function parsePrList(json, totalAvailable) {
|
|
32
63
|
const raw = JSON.parse(json);
|
|
33
64
|
const items = Array.isArray(raw) ? raw : [];
|
|
34
65
|
const prs = items.map((pr) => ({
|
|
@@ -38,62 +69,164 @@ export function parsePrList(json) {
|
|
|
38
69
|
url: pr.url,
|
|
39
70
|
headBranch: pr.headRefName,
|
|
40
71
|
author: pr.author?.login ?? "",
|
|
72
|
+
// S-gap fields
|
|
73
|
+
labels: pr.labels ? pr.labels.map((l) => l.name) : undefined,
|
|
74
|
+
isDraft: pr.isDraft ?? undefined,
|
|
75
|
+
baseBranch: pr.baseRefName ?? undefined,
|
|
76
|
+
reviewDecision: pr.reviewDecision ?? undefined,
|
|
77
|
+
mergeable: pr.mergeable ?? undefined,
|
|
41
78
|
}));
|
|
42
|
-
return { prs, total: prs.length };
|
|
79
|
+
return { prs, total: prs.length, totalAvailable };
|
|
43
80
|
}
|
|
44
81
|
/**
|
|
45
82
|
* Parses `gh pr create` output (URL on stdout) into structured data.
|
|
46
83
|
* The gh CLI prints the new PR URL to stdout. We extract the number from it.
|
|
47
84
|
*/
|
|
48
|
-
export function parsePrCreate(stdout) {
|
|
85
|
+
export function parsePrCreate(stdout, opts) {
|
|
49
86
|
const url = stdout.trim();
|
|
50
87
|
const match = url.match(/\/pull\/(\d+)$/);
|
|
51
88
|
const number = match ? parseInt(match[1], 10) : 0;
|
|
52
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
number,
|
|
91
|
+
url,
|
|
92
|
+
title: opts?.title,
|
|
93
|
+
baseBranch: opts?.baseBranch,
|
|
94
|
+
headBranch: opts?.headBranch,
|
|
95
|
+
draft: opts?.draft,
|
|
96
|
+
};
|
|
53
97
|
}
|
|
54
98
|
/**
|
|
55
99
|
* Parses `gh pr edit` output into structured data.
|
|
56
100
|
* The gh CLI prints the PR URL to stdout on success.
|
|
57
101
|
*/
|
|
58
|
-
export function parsePrUpdate(stdout, number) {
|
|
102
|
+
export function parsePrUpdate(stdout, number, updatedFields, operations) {
|
|
59
103
|
const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
|
|
60
104
|
const url = urlMatch ? urlMatch[1] : "";
|
|
61
|
-
return { number, url };
|
|
105
|
+
return { number, url, updatedFields, operations };
|
|
62
106
|
}
|
|
63
107
|
/**
|
|
64
108
|
* Parses `gh pr merge` output into structured data.
|
|
65
109
|
* The gh CLI prints a confirmation message with the PR URL on success.
|
|
110
|
+
* Enhanced to detect merge commit SHA, auto-merge state, and merge method.
|
|
66
111
|
*/
|
|
67
|
-
export function parsePrMerge(stdout, number, method) {
|
|
112
|
+
export function parsePrMerge(stdout, number, method, deleteBranch, auto, disableAuto) {
|
|
68
113
|
const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
|
|
69
114
|
const url = urlMatch ? urlMatch[1] : "";
|
|
70
|
-
|
|
115
|
+
// S-gap: detect if branch was deleted from output
|
|
116
|
+
const branchDeleted = deleteBranch !== undefined
|
|
117
|
+
? /deleted branch|branch.*deleted/i.test(stdout)
|
|
118
|
+
? true
|
|
119
|
+
: deleteBranch
|
|
120
|
+
: undefined;
|
|
121
|
+
// Extract merge commit SHA if present in output
|
|
122
|
+
// gh pr merge may output lines like "Merge commit SHA: abc123..." or include it in the URL
|
|
123
|
+
const shaMatch = stdout.match(/\b([0-9a-f]{40})\b/);
|
|
124
|
+
const mergeCommitSha = shaMatch ? shaMatch[1] : undefined;
|
|
125
|
+
// Determine the merge state
|
|
126
|
+
let state;
|
|
127
|
+
if (disableAuto) {
|
|
128
|
+
state = "auto-merge-disabled";
|
|
129
|
+
}
|
|
130
|
+
else if (auto || /auto-merge/i.test(stdout) || /automatically merge/i.test(stdout)) {
|
|
131
|
+
state = "auto-merge-enabled";
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
state = "merged";
|
|
135
|
+
}
|
|
136
|
+
// Detect actual merge method from output text when possible
|
|
137
|
+
let detectedMethod = method;
|
|
138
|
+
if (/squashed and merged/i.test(stdout)) {
|
|
139
|
+
detectedMethod = "squash";
|
|
140
|
+
}
|
|
141
|
+
else if (/rebased and merged/i.test(stdout)) {
|
|
142
|
+
detectedMethod = "rebase";
|
|
143
|
+
}
|
|
144
|
+
else if (/merged.*pull request/i.test(stdout) && !/squashed|rebased/i.test(stdout)) {
|
|
145
|
+
detectedMethod = "merge";
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
number,
|
|
149
|
+
merged: state === "merged",
|
|
150
|
+
method: detectedMethod,
|
|
151
|
+
url,
|
|
152
|
+
branchDeleted,
|
|
153
|
+
mergeCommitSha,
|
|
154
|
+
state,
|
|
155
|
+
};
|
|
71
156
|
}
|
|
72
157
|
/**
|
|
73
158
|
* Parses `gh pr comment` / `gh issue comment` output into structured data.
|
|
74
159
|
* The gh CLI prints the new comment URL to stdout.
|
|
160
|
+
* S-gap: Enhanced to include operation type, commentId, number, and body echo.
|
|
75
161
|
*/
|
|
76
|
-
export function parseComment(stdout) {
|
|
162
|
+
export function parseComment(stdout, opts) {
|
|
77
163
|
const url = stdout.trim();
|
|
78
|
-
|
|
164
|
+
// S-gap: extract commentId from URL (e.g., #issuecomment-123456)
|
|
165
|
+
const commentIdMatch = url.match(/#issuecomment-(\d+)/);
|
|
166
|
+
const commentId = commentIdMatch ? commentIdMatch[1] : undefined;
|
|
167
|
+
return {
|
|
168
|
+
url: url || undefined,
|
|
169
|
+
operation: opts?.operation,
|
|
170
|
+
commentId,
|
|
171
|
+
issueNumber: opts?.issueNumber,
|
|
172
|
+
prNumber: opts?.prNumber,
|
|
173
|
+
body: opts?.body,
|
|
174
|
+
};
|
|
79
175
|
}
|
|
80
176
|
/**
|
|
81
177
|
* Parses `gh pr review` output into structured data.
|
|
82
178
|
* The gh CLI prints a confirmation message with the PR URL on success.
|
|
179
|
+
* S-gap: Enhanced to include reviewId, reviewDecision, and body echo.
|
|
83
180
|
*/
|
|
84
|
-
export function parsePrReview(stdout, number, event) {
|
|
181
|
+
export function parsePrReview(stdout, number, event, body, stderr) {
|
|
85
182
|
const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/pull\/\d+)/);
|
|
86
183
|
const url = urlMatch ? urlMatch[1] : "";
|
|
87
|
-
|
|
184
|
+
// P1-gap #145: Parse review event type from CLI output for confirmation
|
|
185
|
+
// Map the CLI event name to GitHub's review event type
|
|
186
|
+
const eventMap = {
|
|
187
|
+
approve: "APPROVE",
|
|
188
|
+
"request-changes": "REQUEST_CHANGES",
|
|
189
|
+
comment: "COMMENT",
|
|
190
|
+
};
|
|
191
|
+
const resolvedEvent = eventMap[event] ?? event;
|
|
192
|
+
// P1-gap #146: Classify review errors from stderr
|
|
193
|
+
const errorType = stderr ? classifyReviewError(stderr) : undefined;
|
|
194
|
+
return {
|
|
195
|
+
number,
|
|
196
|
+
event: resolvedEvent,
|
|
197
|
+
url,
|
|
198
|
+
body: body ?? undefined,
|
|
199
|
+
errorType,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Classifies review error from stderr into structured error types.
|
|
204
|
+
* P1-gap #146.
|
|
205
|
+
*/
|
|
206
|
+
function classifyReviewError(stderr) {
|
|
207
|
+
const lower = stderr.toLowerCase();
|
|
208
|
+
if (/not found|could not resolve|no pull request/i.test(lower))
|
|
209
|
+
return "not-found";
|
|
210
|
+
if (/permission|forbidden|403/i.test(lower))
|
|
211
|
+
return "permission-denied";
|
|
212
|
+
if (/already reviewed|already approved|already submitted/i.test(lower))
|
|
213
|
+
return "already-reviewed";
|
|
214
|
+
if (/draft|is a draft/i.test(lower))
|
|
215
|
+
return "draft-pr";
|
|
216
|
+
if (stderr.trim())
|
|
217
|
+
return "unknown";
|
|
218
|
+
return undefined;
|
|
88
219
|
}
|
|
89
220
|
/**
|
|
90
221
|
* Parses `gh pr checks --json ...` output into structured PR checks data.
|
|
91
222
|
* Computes summary counts by bucket (pass, fail, pending, skipping, cancel).
|
|
223
|
+
* Deduplicates entries by check name, keeping the most recent run
|
|
224
|
+
* (determined by completedAt, then startedAt, with later entries winning ties).
|
|
92
225
|
*/
|
|
93
226
|
export function parsePrChecks(json, pr) {
|
|
94
227
|
const raw = JSON.parse(json);
|
|
95
228
|
const items = Array.isArray(raw) ? raw : [];
|
|
96
|
-
const
|
|
229
|
+
const allChecks = items.map((c) => ({
|
|
97
230
|
name: c.name ?? "",
|
|
98
231
|
state: c.state ?? "",
|
|
99
232
|
bucket: c.bucket ?? "",
|
|
@@ -104,6 +237,25 @@ export function parsePrChecks(json, pr) {
|
|
|
104
237
|
startedAt: c.startedAt ?? "",
|
|
105
238
|
completedAt: c.completedAt ?? "",
|
|
106
239
|
}));
|
|
240
|
+
// Deduplicate by check name, keeping the most recent run.
|
|
241
|
+
// For checks with the same name, prefer the one with the later completedAt
|
|
242
|
+
// (or startedAt as fallback). If timestamps are equal, keep the later entry
|
|
243
|
+
// in the array (which is typically the most recent re-run).
|
|
244
|
+
const deduped = new Map();
|
|
245
|
+
for (const check of allChecks) {
|
|
246
|
+
const existing = deduped.get(check.name);
|
|
247
|
+
if (!existing) {
|
|
248
|
+
deduped.set(check.name, check);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
// Compare by completedAt first, then startedAt
|
|
252
|
+
const existingTime = existing.completedAt || existing.startedAt || "";
|
|
253
|
+
const newTime = check.completedAt || check.startedAt || "";
|
|
254
|
+
if (newTime >= existingTime) {
|
|
255
|
+
deduped.set(check.name, check);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const checks = Array.from(deduped.values());
|
|
107
259
|
const summary = {
|
|
108
260
|
total: checks.length,
|
|
109
261
|
passed: checks.filter((c) => c.bucket === "pass").length,
|
|
@@ -128,6 +280,16 @@ export function parseIssueView(json) {
|
|
|
128
280
|
assignees: (raw.assignees ?? []).map((a) => a.login),
|
|
129
281
|
url: raw.url,
|
|
130
282
|
createdAt: raw.createdAt ?? "",
|
|
283
|
+
// S-gap fields
|
|
284
|
+
stateReason: raw.stateReason ?? undefined,
|
|
285
|
+
author: raw.author?.login ?? undefined,
|
|
286
|
+
milestone: raw.milestone?.title ?? undefined,
|
|
287
|
+
updatedAt: raw.updatedAt ?? undefined,
|
|
288
|
+
closedAt: raw.closedAt ?? undefined,
|
|
289
|
+
isPinned: raw.isPinned ?? undefined,
|
|
290
|
+
projectItems: raw.projectItems
|
|
291
|
+
? raw.projectItems.map((p) => p.title)
|
|
292
|
+
: undefined,
|
|
131
293
|
};
|
|
132
294
|
}
|
|
133
295
|
/**
|
|
@@ -143,39 +305,71 @@ export function parseIssueList(json) {
|
|
|
143
305
|
url: issue.url,
|
|
144
306
|
labels: (issue.labels ?? []).map((l) => l.name),
|
|
145
307
|
assignees: (issue.assignees ?? []).map((a) => a.login),
|
|
308
|
+
// S-gap fields
|
|
309
|
+
author: issue.author?.login ?? undefined,
|
|
310
|
+
createdAt: issue.createdAt ?? undefined,
|
|
311
|
+
milestone: issue.milestone?.title ?? undefined,
|
|
146
312
|
}));
|
|
147
313
|
return { issues, total: issues.length };
|
|
148
314
|
}
|
|
149
315
|
/**
|
|
150
316
|
* Parses `gh issue create` output (URL on stdout) into structured data.
|
|
151
317
|
* The gh CLI prints the new issue URL to stdout. We extract the number from it.
|
|
318
|
+
* S-gap: Enhanced to echo back applied labels.
|
|
152
319
|
*/
|
|
153
|
-
export function parseIssueCreate(stdout) {
|
|
320
|
+
export function parseIssueCreate(stdout, labels) {
|
|
154
321
|
const url = stdout.trim();
|
|
155
322
|
const match = url.match(/\/issues\/(\d+)$/);
|
|
156
323
|
const number = match ? parseInt(match[1], 10) : 0;
|
|
157
|
-
return {
|
|
324
|
+
return {
|
|
325
|
+
number,
|
|
326
|
+
url,
|
|
327
|
+
labelsApplied: labels && labels.length > 0 ? labels : undefined,
|
|
328
|
+
};
|
|
158
329
|
}
|
|
159
330
|
/**
|
|
160
331
|
* Parses `gh issue close` output into structured data.
|
|
161
|
-
* The gh CLI
|
|
332
|
+
* The gh CLI may print a confirmation message plus URL to stdout.
|
|
333
|
+
* We robustly extract the issue URL using a regex rather than assuming
|
|
334
|
+
* the entire stdout is just a URL (handles extra text, whitespace, and
|
|
335
|
+
* different output formats).
|
|
336
|
+
* S-gap: Enhanced to include reason and commentUrl.
|
|
162
337
|
*/
|
|
163
|
-
export function parseIssueClose(stdout, number) {
|
|
164
|
-
|
|
165
|
-
|
|
338
|
+
export function parseIssueClose(stdout, number, reason, comment, stderr) {
|
|
339
|
+
// Robust URL extraction: find the GitHub issue URL anywhere in the output
|
|
340
|
+
const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/issues\/\d+)/);
|
|
341
|
+
const url = urlMatch ? urlMatch[1] : stdout.trim();
|
|
342
|
+
// S-gap: Extract comment URL from output if a comment was added
|
|
343
|
+
const commentUrlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+#issuecomment-\d+)/);
|
|
344
|
+
// P1-gap #144: Detect already-closed issues
|
|
345
|
+
const combined = `${stdout}\n${stderr ?? ""}`;
|
|
346
|
+
const alreadyClosed = /already closed/i.test(combined) ||
|
|
347
|
+
/issue .* is already closed/i.test(combined) ||
|
|
348
|
+
/already been closed/i.test(combined)
|
|
349
|
+
? true
|
|
350
|
+
: undefined;
|
|
351
|
+
return {
|
|
352
|
+
number,
|
|
353
|
+
state: "closed",
|
|
354
|
+
url,
|
|
355
|
+
reason: reason ?? undefined,
|
|
356
|
+
commentUrl: comment && commentUrlMatch ? commentUrlMatch[1] : undefined,
|
|
357
|
+
alreadyClosed,
|
|
358
|
+
};
|
|
166
359
|
}
|
|
167
360
|
/**
|
|
168
361
|
* Parses `gh issue edit` output into structured data.
|
|
169
362
|
* The gh CLI prints the issue URL to stdout on success.
|
|
170
363
|
*/
|
|
171
|
-
export function parseIssueUpdate(stdout, number) {
|
|
364
|
+
export function parseIssueUpdate(stdout, number, updatedFields, operations) {
|
|
172
365
|
const urlMatch = stdout.match(/(https:\/\/github\.com\/[^\s]+\/issues\/\d+)/);
|
|
173
366
|
const url = urlMatch ? urlMatch[1] : "";
|
|
174
|
-
return { number, url };
|
|
367
|
+
return { number, url, updatedFields, operations };
|
|
175
368
|
}
|
|
176
369
|
/**
|
|
177
370
|
* Parses `gh run view --json ...` output into structured run view data.
|
|
178
371
|
* Renames gh field names (e.g., databaseId → id).
|
|
372
|
+
* S-gap: Enhanced to include job steps, headSha, event, startedAt, attempt.
|
|
179
373
|
*/
|
|
180
374
|
export function parseRunView(json) {
|
|
181
375
|
const raw = JSON.parse(json);
|
|
@@ -183,7 +377,24 @@ export function parseRunView(json) {
|
|
|
183
377
|
name: j.name,
|
|
184
378
|
status: j.status,
|
|
185
379
|
conclusion: j.conclusion ?? null,
|
|
380
|
+
// S-gap: Include steps array
|
|
381
|
+
steps: j.steps
|
|
382
|
+
? j.steps.map((s) => ({
|
|
383
|
+
name: s.name,
|
|
384
|
+
status: s.status,
|
|
385
|
+
conclusion: s.conclusion ?? null,
|
|
386
|
+
}))
|
|
387
|
+
: undefined,
|
|
186
388
|
}));
|
|
389
|
+
const updatedAt = raw.updatedAt ?? undefined;
|
|
390
|
+
const startedAt = raw.startedAt ?? undefined;
|
|
391
|
+
const createdAt = raw.createdAt ?? "";
|
|
392
|
+
let durationSeconds;
|
|
393
|
+
const start = startedAt ? Date.parse(startedAt) : createdAt ? Date.parse(createdAt) : NaN;
|
|
394
|
+
const end = updatedAt ? Date.parse(updatedAt) : NaN;
|
|
395
|
+
if (Number.isFinite(start) && Number.isFinite(end) && end >= start) {
|
|
396
|
+
durationSeconds = Math.round((end - start) / 1000);
|
|
397
|
+
}
|
|
187
398
|
return {
|
|
188
399
|
id: raw.databaseId,
|
|
189
400
|
status: raw.status,
|
|
@@ -194,12 +405,19 @@ export function parseRunView(json) {
|
|
|
194
405
|
jobs,
|
|
195
406
|
url: raw.url,
|
|
196
407
|
createdAt: raw.createdAt ?? "",
|
|
408
|
+
// P0 enrichments
|
|
409
|
+
headSha: raw.headSha ?? undefined,
|
|
410
|
+
event: raw.event ?? undefined,
|
|
411
|
+
startedAt,
|
|
412
|
+
updatedAt,
|
|
413
|
+
attempt: raw.attempt ?? undefined,
|
|
414
|
+
durationSeconds,
|
|
197
415
|
};
|
|
198
416
|
}
|
|
199
417
|
/**
|
|
200
418
|
* Parses `gh run list --json ...` output into structured run list data.
|
|
201
419
|
*/
|
|
202
|
-
export function parseRunList(json) {
|
|
420
|
+
export function parseRunList(json, totalAvailable) {
|
|
203
421
|
const raw = JSON.parse(json);
|
|
204
422
|
const items = Array.isArray(raw) ? raw : [];
|
|
205
423
|
const runs = items.map((r) => ({
|
|
@@ -211,40 +429,66 @@ export function parseRunList(json) {
|
|
|
211
429
|
headBranch: r.headBranch,
|
|
212
430
|
url: r.url,
|
|
213
431
|
createdAt: r.createdAt ?? "",
|
|
432
|
+
// P1-gap #148
|
|
433
|
+
headSha: r.headSha ?? undefined,
|
|
434
|
+
event: r.event ?? undefined,
|
|
435
|
+
startedAt: r.startedAt ?? undefined,
|
|
436
|
+
attempt: r.attempt ?? undefined,
|
|
214
437
|
}));
|
|
215
|
-
return { runs, total: runs.length };
|
|
438
|
+
return { runs, total: runs.length, totalAvailable };
|
|
216
439
|
}
|
|
217
440
|
/**
|
|
218
441
|
* Parses `gh run rerun` output into structured data.
|
|
219
442
|
* The gh CLI prints a confirmation message to stderr on success.
|
|
220
443
|
* We construct the URL from the run ID and repo info.
|
|
444
|
+
* S-gap: Enhanced to include job field.
|
|
221
445
|
*/
|
|
222
|
-
export function parseRunRerun(stdout, stderr, runId, failedOnly) {
|
|
446
|
+
export function parseRunRerun(stdout, stderr, runId, failedOnly, job) {
|
|
223
447
|
// gh run rerun outputs confirmation to stderr, e.g.:
|
|
224
448
|
// "✓ Requested rerun of run 12345"
|
|
225
449
|
// Try to extract URL from stdout/stderr if present
|
|
226
450
|
const combined = `${stdout}\n${stderr}`;
|
|
227
451
|
const urlMatch = combined.match(/(https:\/\/github\.com\/[^\s]+\/actions\/runs\/\d+)/);
|
|
228
452
|
const url = urlMatch ? urlMatch[1] : "";
|
|
453
|
+
// P1-gap #149: Parse attempt number from output
|
|
454
|
+
const attemptMatch = combined.match(/attempt[\s#:]*?(\d+)/i);
|
|
455
|
+
const attempt = attemptMatch ? parseInt(attemptMatch[1], 10) : undefined;
|
|
456
|
+
// P1-gap #149: Extract new run URL if a different URL appears (for the new attempt)
|
|
457
|
+
// Sometimes gh outputs the new run URL after a rerun
|
|
458
|
+
const allUrls = combined.match(/https:\/\/github\.com\/[^\s]+\/actions\/runs\/\d+/g) ?? [];
|
|
459
|
+
const newRunUrl = allUrls.length > 1 ? allUrls[allUrls.length - 1] : undefined;
|
|
460
|
+
const status = job ? "requested-job" : failedOnly ? "requested-failed" : "requested-full";
|
|
229
461
|
return {
|
|
230
462
|
runId,
|
|
231
|
-
status
|
|
463
|
+
status,
|
|
232
464
|
failedOnly,
|
|
233
465
|
url,
|
|
466
|
+
job: job ?? undefined,
|
|
467
|
+
attempt,
|
|
468
|
+
newRunUrl,
|
|
234
469
|
};
|
|
235
470
|
}
|
|
236
471
|
/**
|
|
237
472
|
* Parses `gh release create` output (URL on stdout) into structured data.
|
|
238
473
|
* The gh CLI prints the new release URL to stdout.
|
|
474
|
+
* S-gap: Enhanced to include title, assetsUploaded count.
|
|
239
475
|
*/
|
|
240
|
-
export function parseReleaseCreate(stdout, tag, draft, prerelease) {
|
|
476
|
+
export function parseReleaseCreate(stdout, tag, draft, prerelease, title, assetsCount) {
|
|
241
477
|
const url = stdout.trim();
|
|
242
|
-
return {
|
|
478
|
+
return {
|
|
479
|
+
tag,
|
|
480
|
+
url,
|
|
481
|
+
draft,
|
|
482
|
+
prerelease,
|
|
483
|
+
title: title ?? undefined,
|
|
484
|
+
assetsUploaded: assetsCount ?? undefined,
|
|
485
|
+
};
|
|
243
486
|
}
|
|
244
487
|
/**
|
|
245
488
|
* Parses `gh release list --json ...` output into structured release list data.
|
|
489
|
+
* S-gap: Enhanced to include isLatest and createdAt.
|
|
246
490
|
*/
|
|
247
|
-
export function parseReleaseList(json) {
|
|
491
|
+
export function parseReleaseList(json, totalAvailable) {
|
|
248
492
|
const raw = JSON.parse(json);
|
|
249
493
|
const items = Array.isArray(raw) ? raw : [];
|
|
250
494
|
const releases = items.map((r) => ({
|
|
@@ -254,34 +498,178 @@ export function parseReleaseList(json) {
|
|
|
254
498
|
prerelease: r.isPrerelease ?? false,
|
|
255
499
|
publishedAt: r.publishedAt ?? "",
|
|
256
500
|
url: r.url ?? "",
|
|
501
|
+
// S-gap fields
|
|
502
|
+
isLatest: r.isLatest ?? undefined,
|
|
503
|
+
createdAt: r.createdAt ?? undefined,
|
|
257
504
|
}));
|
|
258
|
-
return { releases, total: releases.length };
|
|
505
|
+
return { releases, total: releases.length, totalAvailable };
|
|
259
506
|
}
|
|
260
507
|
/**
|
|
261
508
|
* Parses `gh api` stdout into structured API result data.
|
|
262
|
-
*
|
|
509
|
+
* When `--include` is used, the output starts with HTTP headers followed by
|
|
510
|
+
* a blank line and then the response body. We parse the real HTTP status code
|
|
511
|
+
* from the status line and separate the body.
|
|
263
512
|
*/
|
|
264
|
-
export function parseApi(stdout, exitCode, endpoint, method) {
|
|
265
|
-
//
|
|
513
|
+
export function parseApi(stdout, exitCode, endpoint, method, stderr) {
|
|
514
|
+
// Default: infer status from exit code
|
|
515
|
+
let statusCode = exitCode === 0 ? 200 : 422;
|
|
516
|
+
let bodyText = stdout;
|
|
517
|
+
let responseHeaders;
|
|
518
|
+
let pagination;
|
|
519
|
+
// When --include is passed, stdout starts with HTTP headers:
|
|
520
|
+
// HTTP/2.0 200 OK\r\n...headers...\r\n\r\nbody
|
|
521
|
+
const headerEndIndex = stdout.indexOf("\r\n\r\n");
|
|
522
|
+
if (headerEndIndex !== -1) {
|
|
523
|
+
const headerBlock = stdout.slice(0, headerEndIndex);
|
|
524
|
+
bodyText = stdout.slice(headerEndIndex + 4);
|
|
525
|
+
responseHeaders = parseHeaderBlock(headerBlock);
|
|
526
|
+
pagination = parsePagination(responseHeaders?.link);
|
|
527
|
+
// Parse status code from first line: "HTTP/1.1 200 OK" or "HTTP/2.0 200 OK"
|
|
528
|
+
const statusMatch = headerBlock.match(/^HTTP\/[\d.]+ (\d+)/);
|
|
529
|
+
if (statusMatch) {
|
|
530
|
+
statusCode = parseInt(statusMatch[1], 10);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
// Also handle \n\n separator (some environments)
|
|
535
|
+
const headerEndLF = stdout.indexOf("\n\n");
|
|
536
|
+
if (headerEndLF !== -1 && /^HTTP\/[\d.]+ \d+/.test(stdout)) {
|
|
537
|
+
const headerBlock = stdout.slice(0, headerEndLF);
|
|
538
|
+
bodyText = stdout.slice(headerEndLF + 2);
|
|
539
|
+
responseHeaders = parseHeaderBlock(headerBlock);
|
|
540
|
+
pagination = parsePagination(responseHeaders?.link);
|
|
541
|
+
const statusMatch = headerBlock.match(/^HTTP\/[\d.]+ (\d+)/);
|
|
542
|
+
if (statusMatch) {
|
|
543
|
+
statusCode = parseInt(statusMatch[1], 10);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Keep legacy `status` as before for backward compat
|
|
266
548
|
const status = exitCode === 0 ? 200 : 422;
|
|
267
549
|
let body;
|
|
268
550
|
try {
|
|
269
|
-
body = JSON.parse(
|
|
551
|
+
body = JSON.parse(bodyText);
|
|
270
552
|
}
|
|
271
553
|
catch {
|
|
272
|
-
body =
|
|
554
|
+
body = bodyText;
|
|
555
|
+
}
|
|
556
|
+
// P1-gap #141: Preserve error body for debugging when request failed
|
|
557
|
+
const errorBody = exitCode !== 0 && stderr ? parseErrorBody(stderr) : undefined;
|
|
558
|
+
// GraphQL responses can return 200 with an `errors` array.
|
|
559
|
+
const graphqlErrors = body && typeof body === "object" && "errors" in body
|
|
560
|
+
? (body.errors ?? undefined)
|
|
561
|
+
: undefined;
|
|
562
|
+
return {
|
|
563
|
+
status,
|
|
564
|
+
statusCode,
|
|
565
|
+
body,
|
|
566
|
+
endpoint,
|
|
567
|
+
method,
|
|
568
|
+
responseHeaders,
|
|
569
|
+
pagination,
|
|
570
|
+
graphqlErrors,
|
|
571
|
+
errorBody,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function parseHeaderBlock(headerBlock) {
|
|
575
|
+
const lines = headerBlock.split(/\r?\n/);
|
|
576
|
+
const headers = {};
|
|
577
|
+
for (const line of lines.slice(1)) {
|
|
578
|
+
const idx = line.indexOf(":");
|
|
579
|
+
if (idx <= 0)
|
|
580
|
+
continue;
|
|
581
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
582
|
+
const value = line.slice(idx + 1).trim();
|
|
583
|
+
headers[key] = value;
|
|
584
|
+
}
|
|
585
|
+
return headers;
|
|
586
|
+
}
|
|
587
|
+
function parsePagination(linkHeader) {
|
|
588
|
+
if (!linkHeader)
|
|
589
|
+
return undefined;
|
|
590
|
+
const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel=\"next\"/);
|
|
591
|
+
const lastMatch = linkHeader.match(/<([^>]+)>;\s*rel=\"last\"/);
|
|
592
|
+
return {
|
|
593
|
+
hasNext: !!nextMatch,
|
|
594
|
+
next: nextMatch?.[1],
|
|
595
|
+
last: lastMatch?.[1],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Attempts to parse error body from stderr output.
|
|
600
|
+
* gh api may output JSON error details or plain text to stderr.
|
|
601
|
+
*/
|
|
602
|
+
function parseErrorBody(stderr) {
|
|
603
|
+
const trimmed = stderr.trim();
|
|
604
|
+
if (!trimmed)
|
|
605
|
+
return undefined;
|
|
606
|
+
try {
|
|
607
|
+
return JSON.parse(trimmed);
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
// Check if stderr contains an embedded JSON object (e.g., after a prefix message)
|
|
611
|
+
const jsonMatch = trimmed.match(/\{[\s\S]*\}/);
|
|
612
|
+
if (jsonMatch) {
|
|
613
|
+
try {
|
|
614
|
+
return JSON.parse(jsonMatch[0]);
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
// fall through
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return trimmed;
|
|
273
621
|
}
|
|
274
|
-
return { status, body, endpoint, method };
|
|
275
622
|
}
|
|
276
623
|
/**
|
|
277
624
|
* Parses `gh gist create` output (URL on stdout) into structured data.
|
|
278
625
|
* The gh CLI prints the new gist URL to stdout. We extract the ID from it.
|
|
626
|
+
* S-gap: Enhanced to include files, description, fileCount.
|
|
279
627
|
*/
|
|
280
|
-
export function parseGistCreate(stdout, isPublic) {
|
|
628
|
+
export function parseGistCreate(stdout, isPublic, files, description) {
|
|
281
629
|
const url = stdout.trim();
|
|
282
|
-
const match = url.match(
|
|
630
|
+
const match = url.match(/gist\.github\.com\/(?:[^/]+\/)?([a-f0-9]+)/i) ||
|
|
631
|
+
url.match(/\/([a-f0-9]+)(?:\/?$|[?#])/i);
|
|
283
632
|
const id = match ? match[1] : "";
|
|
284
|
-
return {
|
|
633
|
+
return {
|
|
634
|
+
id,
|
|
635
|
+
url,
|
|
636
|
+
public: isPublic,
|
|
637
|
+
files: files ?? undefined,
|
|
638
|
+
description: description ?? undefined,
|
|
639
|
+
fileCount: files ? files.length : undefined,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Parses `gh label list --json ...` output into structured label list data.
|
|
644
|
+
*/
|
|
645
|
+
export function parseLabelList(json) {
|
|
646
|
+
const raw = JSON.parse(json);
|
|
647
|
+
const items = Array.isArray(raw) ? raw : [];
|
|
648
|
+
const labels = items.map((l) => ({
|
|
649
|
+
name: l.name ?? "",
|
|
650
|
+
description: l.description ?? "",
|
|
651
|
+
color: l.color ?? "",
|
|
652
|
+
isDefault: l.isDefault ?? false,
|
|
653
|
+
}));
|
|
654
|
+
return { labels, total: labels.length };
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Parses `gh label create` output into structured label create data.
|
|
658
|
+
* The gh CLI prints a confirmation message to stderr on success.
|
|
659
|
+
*/
|
|
660
|
+
export function parseLabelCreate(stdout, stderr, name, description, color) {
|
|
661
|
+
// gh label create outputs confirmation to stderr, e.g.:
|
|
662
|
+
// "✓ Label "my-label" created in owner/repo"
|
|
663
|
+
// Try to extract a URL if present
|
|
664
|
+
const combined = `${stdout}\n${stderr}`;
|
|
665
|
+
const urlMatch = combined.match(/(https:\/\/github\.com\/[^\s]+\/labels\/[^\s]+)/);
|
|
666
|
+
const url = urlMatch ? urlMatch[1] : undefined;
|
|
667
|
+
return {
|
|
668
|
+
name,
|
|
669
|
+
description: description ?? undefined,
|
|
670
|
+
color: color ?? undefined,
|
|
671
|
+
url,
|
|
672
|
+
};
|
|
285
673
|
}
|
|
286
674
|
/**
|
|
287
675
|
* Parses `gh pr diff --numstat` output into structured PR diff data.
|