Skip to content

Commit c3e2383

Browse files
committed
add upsert pr
1 parent 42cc74d commit c3e2383

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { readFileSync } from "node:fs";
2+
3+
import { parseArgs, writeGithubOutput } from "./_shared.mjs";
4+
5+
const args = parseArgs();
6+
const repository = process.env.GITHUB_REPOSITORY;
7+
const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
8+
const base = args.base;
9+
const head = args.head;
10+
const title = args.title;
11+
const body = args["body-file"]
12+
? readFileSync(args["body-file"], "utf8")
13+
: (args.body ?? "");
14+
15+
if (!repository || !token) {
16+
throw new Error(
17+
"GITHUB_REPOSITORY and GITHUB_TOKEN (or GH_TOKEN) are required",
18+
);
19+
}
20+
21+
if (!base || !head || !title || !body.trim()) {
22+
throw new Error("--base, --head, --title, and a non-empty body are required");
23+
}
24+
25+
const [owner] = repository.split("/");
26+
const existingPullRequest = await findPullRequest(
27+
repository,
28+
owner,
29+
base,
30+
head,
31+
);
32+
33+
let pullRequest;
34+
let action;
35+
36+
if (!existingPullRequest) {
37+
pullRequest = await request(`/repos/${repository}/pulls`, token, {
38+
method: "POST",
39+
body: JSON.stringify({
40+
base,
41+
body,
42+
head,
43+
title,
44+
}),
45+
});
46+
action = "created";
47+
} else if (
48+
existingPullRequest.title !== title ||
49+
existingPullRequest.body !== body ||
50+
existingPullRequest.base?.ref !== base
51+
) {
52+
pullRequest = await request(
53+
`/repos/${repository}/pulls/${existingPullRequest.number}`,
54+
token,
55+
{
56+
method: "PATCH",
57+
body: JSON.stringify({
58+
base,
59+
body,
60+
title,
61+
}),
62+
},
63+
);
64+
action = "updated";
65+
} else {
66+
pullRequest = existingPullRequest;
67+
action = "existing";
68+
}
69+
70+
writeGithubOutput("action", action);
71+
writeGithubOutput("number", pullRequest.number);
72+
writeGithubOutput("url", pullRequest.html_url);
73+
writeGithubOutput("title", pullRequest.title);
74+
75+
console.log(
76+
`${action} pull request #${pullRequest.number}: ${pullRequest.html_url}`,
77+
);
78+
79+
async function findPullRequest(
80+
currentRepository,
81+
currentOwner,
82+
currentBase,
83+
currentHead,
84+
) {
85+
const headQuery = encodeURIComponent(`${currentOwner}:${currentHead}`);
86+
const baseQuery = encodeURIComponent(currentBase);
87+
const pullRequests = await request(
88+
`/repos/${currentRepository}/pulls?state=open&base=${baseQuery}&head=${headQuery}&per_page=1`,
89+
token,
90+
{ method: "GET" },
91+
);
92+
93+
return pullRequests[0] ?? null;
94+
}
95+
96+
async function request(endpoint, authToken, options) {
97+
const response = await fetch(`https://api.github.com${endpoint}`, {
98+
method: options.method,
99+
headers: {
100+
Accept: "application/vnd.github+json",
101+
Authorization: `Bearer ${authToken}`,
102+
"Content-Type": "application/json",
103+
"X-GitHub-Api-Version": "2022-11-28",
104+
},
105+
body: options.body,
106+
});
107+
108+
if (!response.ok) {
109+
const errorBody = await response.text();
110+
throw new Error(
111+
`${options.method} ${endpoint} failed: ${response.status} ${errorBody}`,
112+
);
113+
}
114+
115+
return response.json();
116+
}

0 commit comments

Comments
 (0)