-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub.ts
More file actions
123 lines (111 loc) Β· 3.73 KB
/
Copy pathgithub.ts
File metadata and controls
123 lines (111 loc) Β· 3.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Push a local review to a GitHub pull request as inline review comments.
// Network access happens only here, and only when the caller opts in via
// --push-pr β the rest of revu stays local-first.
import { type Annotation, parseCommentKey } from "./model.ts"
const sideToGh = (side: "old" | "new"): "LEFT" | "RIGHT" =>
side === "old" ? "LEFT" : "RIGHT"
// Formats an annotation as a PR comment body: severity as a conventional-comment
// prefix, status when non-default, and a small revu attribution.
const formatBody = (ann: Annotation): string => {
const prefix = ann.severity ? `**${ann.severity}:** ` : ""
const status =
ann.status && ann.status !== "open" ? `\n\n_status: ${ann.status}_` : ""
return `${prefix}${ann.text}${status}\n\n_β via revu_`
}
export interface ReviewComment {
path: string
line: number
side: "LEFT" | "RIGHT"
start_line?: number
start_side?: "LEFT" | "RIGHT"
body: string
}
// Maps each annotation to a GitHub review comment. Single-line annotations use
// `line`/`side`; ranges add `start_line`/`start_side`.
export const buildPrReviewPayload = (
comments: Map<string, Annotation>,
): ReviewComment[] => {
const out: ReviewComment[] = []
for (const [key, ann] of comments) {
const { file, side, startLine, endLine } = parseCommentKey(key)
const ghSide = sideToGh(side)
const comment: ReviewComment = {
path: file,
line: endLine,
side: ghSide,
body: formatBody(ann),
}
if (endLine !== startLine) {
comment.start_line = startLine
comment.start_side = ghSide
}
out.push(comment)
}
return out
}
export interface PrRef {
number: number
nameWithOwner: string
}
// Best-effort detection of the open PR for the current branch. Returns null if
// gh is unavailable, unauthenticated, or no PR exists.
export const detectPr = async (cwd: string): Promise<PrRef | null> => {
try {
const numberText = (
await Bun.$`gh pr view --json number -q .number`.cwd(cwd).text()
).trim()
const number = parseInt(numberText, 10)
if (!Number.isFinite(number)) return null
const nameWithOwner = (
await Bun.$`gh repo view --json nameWithOwner -q .nameWithOwner`
.cwd(cwd)
.text()
).trim()
if (!nameWithOwner) return null
return { number, nameWithOwner }
} catch {
return null
}
}
export interface PushOptions {
cwd: string
comments: Map<string, Annotation>
dryRun: boolean
}
// Posts the review to the current branch's PR. With dryRun, prints the payload
// (and the resolved target, best-effort) without any mutation.
export const pushReviewToPr = async (opts: PushOptions): Promise<void> => {
const { cwd, comments, dryRun } = opts
const reviewComments = buildPrReviewPayload(comments)
const pr = await detectPr(cwd)
const body = {
event: "COMMENT",
body: `revu review β ${reviewComments.length} annotation(s)`,
comments: reviewComments,
}
if (dryRun) {
const target = pr
? `${pr.nameWithOwner}#${pr.number}`
: "(no PR detected for the current branch)"
console.log(
`[dry-run] Would post ${reviewComments.length} comment(s) to ${target}:`,
)
console.log(JSON.stringify(body, null, 2))
return
}
if (!pr) {
throw new Error("No open pull request found for the current branch.")
}
const payloadPath = `${process.env.TMPDIR ?? "/tmp"}/revu-pr-review-${pr.number}.json`
await Bun.write(payloadPath, JSON.stringify(body))
try {
await Bun.$`gh api repos/${pr.nameWithOwner}/pulls/${pr.number}/reviews -X POST --input ${payloadPath}`
.cwd(cwd)
.quiet()
} finally {
await Bun.$`rm -f ${payloadPath}`.nothrow().quiet()
}
console.log(
`Posted ${reviewComments.length} comment(s) to ${pr.nameWithOwner}#${pr.number}.`,
)
}