tools: update `create-release-proposal` workflow · nodejs/node@12baefb
@@ -2,6 +2,9 @@
2233set -xe
445+GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-nodejs/node}
6+BOT_TOKEN=${BOT_TOKEN:-}
7+58RELEASE_DATE=$1
69RELEASE_LINE=$2
710@@ -10,24 +13,114 @@ if [ -z "$RELEASE_DATE" ] || [ -z "$RELEASE_LINE" ]; then
1013exit 1
1114fi
121516+if [ -z "$GITHUB_REPOSITORY" ] || [ -z "$BOT_TOKEN" ]; then
17+echo "Invalid value in env for GITHUB_REPOSITORY and BOT_TOKEN"
18+exit 1
19+fi
20+21+if ! command -v node || ! command -v gh || ! command -v git || ! command -v awk; then
22+echo "Missing required dependencies"
23+exit 1
24+fi
25+1326git node release --prepare --skipBranchDiff --yes --releaseDate "$RELEASE_DATE"
14-# We use it to not specify the branch name as it changes based on
15-# the commit list (semver-minor/semver-patch)
16-git config push.default current
17-git push
27+28+HEAD_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
29+HEAD_SHA="$(git rev-parse HEAD^)"
18301931TITLE=$(awk "/^## ${RELEASE_DATE}/ { print substr(\$0, 4) }" "doc/changelogs/CHANGELOG_V${RELEASE_LINE}.md")
20322133# Use a temporary file for the PR body
2234TEMP_BODY="$(awk "/## ${RELEASE_DATE}/,/^<a id=/{ if (!/^<a id=/) print }" "doc/changelogs/CHANGELOG_V${RELEASE_LINE}.md")"
233524-PR_URL="$(gh pr create --title "$TITLE" --body "$TEMP_BODY" --base "v$RELEASE_LINE.x")"
36+# Create the proposal branch
37+gh api \
38+ --method POST \
39+ -H "Accept: application/vnd.github+json" \
40+ -H "X-GitHub-Api-Version: 2022-11-28" \
41+"/repos/${GITHUB_REPOSITORY}/git/refs" \
42+ -f "ref=refs/heads/$HEAD_BRANCH" -f "sha=$HEAD_SHA"
43+44+# Create the proposal PR
45+PR_URL="$(gh api \
46+ --method POST \
47+ --jq .html_url \
48+ -H "Accept: application/vnd.github+json" \
49+ -H "X-GitHub-Api-Version: 2022-11-28" \
50+ "/repos/${GITHUB_REPOSITORY}/pulls" \
51+ -f "title=$TITLE" -f "body=$TEMP_BODY" -f "head=$HEAD_BRANCH" -f "base=v$RELEASE_LINE.x")"
255226-# Amend commit message so it contains the correct PR-URL trailer.
27-AMENDED_COMMIT_MSG="$(git log -1 --pretty=%B | sed "s|PR-URL: TODO|PR-URL: $PR_URL|")"
53+# Push the release commit to the proposal branch using `BOT_TOKEN` from the env
54+node --input-type=module - \
55+"$GITHUB_REPOSITORY" \
56+"$HEAD_BRANCH" \
57+"$HEAD_SHA" \
58+"$(git log -1 HEAD --format=%s || true)" \
59+"$(git log -1 HEAD --format=%b | awk -v PR_URL="$PR_URL" '{sub(/^PR-URL: TODO$/, "PR-URL: " PR_URL)} 1' || true)" \
60+"$(git show HEAD --diff-filter=d --name-only --format= || true)" \
61+"$(git show HEAD --diff-filter=D --name-only --format= || true)" \
62+<<'EOF'
63+const [,,
64+ repo,
65+ branch,
66+ parentCommitSha,
67+ commit_title,
68+ commit_body,
69+ modifiedOrAddedFiles,
70+ deletedFiles,
71+] = process.argv;
287229-# Replace "TODO" with the PR URL in the last commit
30-git commit --amend --no-edit -m "$AMENDED_COMMIT_MSG" || true
73+import { readFileSync } from 'node:fs';
74+import util from 'node:util';
317532-# Force-push the amended commit
33-git push --force
76+const query = `
77+mutation ($repo: String! $branch: String!, $parentCommitSha: GitObjectID!, $changes: FileChanges!, $commit_title: String!, $commit_body: String) {
78+ createCommitOnBranch(input: {
79+ branch: {
80+ repositoryNameWithOwner: $repo,
81+ branchName: $branch
82+ },
83+ message: {
84+ headline: $commit_title,
85+ body: $commit_body
86+ },
87+ expectedHeadOid: $parentCommitSha,
88+ fileChanges: $changes
89+ }) {
90+ commit {
91+ url
92+ }
93+ }
94+}
95+`;
96+const response = await fetch('https://api.github.com/graphql', {
97+ method: 'POST',
98+ headers: {
99+ 'Authorization': `bearer ${process.env.BOT_TOKEN}`,
100+ },
101+ body: JSON.stringify({
102+ query,
103+ variables: {
104+ repo,
105+ branch,
106+ parentCommitSha,
107+ commit_title,
108+ commit_body,
109+ changes: {
110+ additions: modifiedOrAddedFiles.split('\n').filter(Boolean)
111+ .map(path => ({ path, contents: readFileSync(path).toString('base64') })),
112+ deletions: deletedFiles.split('\n').filter(Boolean),
113+ }
114+ },
115+ })
116+});
117+if (!response.ok) {
118+ console.log({statusCode: response.status, statusText: response.statusText});
119+ process.exitCode ||= 1;
120+}
121+const data = await response.json();
122+if (data.errors?.length) {
123+ throw new Error('Endpoint returned an error', { cause: data });
124+}
125+console.log(util.inspect(data, { depth: Infinity }));
126+EOF