-
Notifications
You must be signed in to change notification settings - Fork 6
381 lines (322 loc) · 18.3 KB
/
open-version-bump-pr.yaml
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
name: 'Open Version Bump PR'
on:
workflow_dispatch:
inputs:
version-type:
description: 'How to tick the version (none, patch, minor, major)'
required: true
default: 'none'
dry-run:
description: 'If true, skips commit on npm version and passes the --dry-run flag to npm publish. Useful for testing.'
required: false
default: 'false'
pull_request:
types:
- closed
jobs:
preflight: # Check that labels include a version bump, and extract the version bump label
runs-on: ubuntu-latest
if: (github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' )
outputs:
MESSAGE: ${{ steps.validate.outputs.MESSAGE }}
JOB_NAME: ${{ steps.set-job-name.outputs.JOB_NAME }}
VERSION_INSTRUCTION: ${{ steps.validate.outputs.VERSION_INSTRUCTION }}
steps:
- id: set-job-name
run: echo "JOB_NAME=${{ github.job }}" >> $GITHUB_OUTPUT
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- id: validate
name: Validate that version bump labels are on the PR
uses: ./.github/actions/validate-version-labels
with:
LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') || inputs.version-type }}
createVersionBumpPR:
if: (github.event.pull_request.merged == true || inputs.version-type != 'none' || inputs.dry-run == 'true') && needs.preflight.outputs.VERSION_INSTRUCTION != 'version-bump'
runs-on: ubuntu-latest
needs: preflight # Will be skipped if preflight is skipped, which causes this to not run on closes w/o merge unless it's a manual run
outputs:
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
PR_URL: ${{ steps.create-pr.outputs.pull-request-url }}
MESSAGE: ${{ steps.validate-pr-change.outputs.MESSAGE || steps.tick-version.outputs.MESSAGE }}
JOB_NAME: ${{ steps.set-job-name.outputs.JOB_NAME }}
VERSION_TAG: ${{ steps.tick-version.outputs.VERSION_TAG }}
steps:
- id: set-job-name
run: echo "JOB_NAME=${{ github.job }}" >> $GITHUB_OUTPUT
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.base_ref || github.ref }} # Explicit ref required to push from PR because of github internals
- uses: ./.github/actions/set-git-credentials
- uses: ./.github/actions/setup
- id: tick-version
run: |
VERSION_INSTRUCTION=${{ needs.preflight.outputs.VERSION_INSTRUCTION }}
if [[ $VERSION_INSTRUCTION == "none" ]]; then
echo "No version bump required"
exit 0
elif [[ -z $VERSION_INSTRUCTION ]]; then
MESSAGE="`createVersionBumpPR failed` because no `VERSION_INSTRUCTION` was provided. This is most likely because the `preflight` job or [validate_version_labels](./.github/actions/validate_version_labels) isn't producing output."
echo "MESSAGE=$MESSAGE" >> $GITHUB_OUTPUT
exit 1
fi
if ! npm version $VERSION_INSTRUCTION ; then
MESSAGE="Failed to tick version with `npm version $VERSION_INSTRUCTION`"
echo "MESSAGE=$MESSAGE" >> $GITHUB_OUTPUT
exit 1
fi
# Can't use `npm show . version` because that will show the version of the package in the registry, not the version in package.json, and we haven't published yet
PACKAGE_VERSION=$(cat package.json \
| grep version \
| head -1 \
| awk -F: '{ print $2 }' \
| sed 's/[", ]//g')
echo "VERSION_TAG=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
- name: Create commit
id: create-commit
if: inputs.dry-run != 'true' && ${{ steps.tick-version.outputs.VERSION_TAG != '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FILE_TO_COMMIT: package.json
VERSION_BRANCH: 'version-bump'
run: |
# start with creating new branch 'version-bump', also update the reference to the origin if it doesn't exist
git checkout -b $VERSION_BRANCH
if ! gh api -X GET /repos/:owner/:repo/git/ref/heads/$VERSION_BRANCH >/dev/null 2>&1; then
echo "creating new branch $VERSION_BRANCH"
gh api -X POST /repos/:owner/:repo/git/refs -f "ref=refs/heads/$VERSION_BRANCH" -f sha="$GITHUB_SHA"
fi
# get the content of package.json from $VERSION_BRANCH to get the current bump version
PACKAGE_JSON_CONTENT=$(gh api --method GET /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
-f "ref=refs/heads/$VERSION_BRANCH" \
| jq -r '.content' \
| base64 -d)
BUMP_VERSION=$(echo "$PACKAGE_JSON_CONTENT" | jq -r '.version')
NEW_VERSION="${{ steps.tick-version.outputs.VERSION_TAG }}"
echo "BUMP_VERSION $BUMP_VERSION"
echo "NEW_VERSION $NEW_VERSION"
#We need to compare and update the package version in version bump PR only if the new version is higher then current bump version.
#For example if PR labeled as 'minor' is merged, then only 'major' labeled PR will overwrite the version change, not the 'patch' or 'minor' labeled PRs.
# Split versions into major, minor, and patch components
IFS='.' read -r -a bump <<< "$BUMP_VERSION"
IFS='.' read -r -a new <<< "$NEW_VERSION"
HIGHEST_VERSION="equal"
# Compare major version number
if (( bump[0] > new[0] )); then
HIGHEST_VERSION="bump"
elif (( bump[0] < new[0] )); then
HIGHEST_VERSION="new"
else
# Compare minor version number
if (( bump[1] > new[1] )); then
HIGHEST_VERSION="bump"
elif (( bump[1] < new[1] )); then
HIGHEST_VERSION="new"
else
# Compare patch version number
if (( bump[2] > new[2] )); then
HIGHEST_VERSION="bump"
elif (( bump[2] < new[2] )); then
HIGHEST_VERSION="new"
fi
fi
fi
if [[ "$HIGHEST_VERSION" == "new" ]]; then
#new version is greater than current bump version, creating new commit
# move the branch pointer one commit backwards so that we can manually commit changes done by 'npm version ...' command
git reset HEAD~
# create a commit with content of package.json. This will give us 'verified' commit label from github actions bot
MESSAGE="${{ steps.tick-version.outputs.VERSION_TAG }}"
SHA=$(gh api --method GET /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
-f "ref=refs/heads/$VERSION_BRANCH" \
--jq '.sha')
CONTENT=$( base64 -i $FILE_TO_COMMIT )
NEW_COMMIT_SHA=$(gh api --method PUT /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
--field message="$MESSAGE" \
--field content="$CONTENT" \
--field encoding="base64" \
--field branch="$VERSION_BRANCH" \
--field sha="$SHA" | jq -r '.commit.sha')
echo "UPDATED_VERSION_TAG=$NEW_VERSION" >> $GITHUB_OUTPUT
# create a tag from VERSION_TAG
TAG_RESPONSE=$(gh api --method POST /repos/:owner/:repo/git/tags \
--field tag="v${{ steps.tick-version.outputs.VERSION_TAG }}" \
--field message="${{ steps.tick-version.outputs.VERSION_TAG }}" \
--field object="$NEW_COMMIT_SHA" \
--field type="commit")
NEW_TAG_SHA=$(echo "$TAG_RESPONSE" | jq -r '.sha')
# update the reference so that the tag is visible in github
gh api --method POST /repos/:owner/:repo/git/refs \
--field ref="refs/tags/v${{ steps.tick-version.outputs.VERSION_TAG }}" \
--field sha="$NEW_TAG_SHA"
fi
echo "VERSION_BRANCH=$VERSION_BRANCH" >> $GITHUB_OUTPUT
- name: Update code coverage badge
if: ${{ steps.tick-version.outputs.VERSION_TAG != '' }}
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# get the current and new code coverage result and update the coverage badge if needed
VERSION_BRANCH="${{ steps.create-commit.outputs.VERSION_BRANCH }}"
COVERAGE_BRANCH="coverage-pr-${{ github.event.number }}"
COVERAGE_FILE="coverage/coverage-summary.json"
# read the content of readme from $VERSION_BRANCH
README_CONTENT=$(gh api --method GET /repos/:owner/:repo/contents/README.md \
-f "ref=refs/heads/$VERSION_BRANCH" \
| jq -r '.content' \
| base64 -d)
CURRENT_COVERAGE=$(echo "$README_CONTENT" | grep -o 'coverage-[0-9]*\(\.[0-9]\{1,\}\)\?%' | sed 's/coverage-\(.*\)%/\1/')
echo "Current code coverage - $CURRENT_COVERAGE"
# read the content of coverage-report.json from $COVERAGE_BRANCH and get the coverage number for statements
COVERAGE_REPORT=$(gh api --method GET /repos/:owner/:repo/contents/$COVERAGE_FILE \
-f "ref=refs/heads/$COVERAGE_BRANCH" \
| jq -r '.content' \
| base64 -d)
NEW_COVERAGE=$(echo "$COVERAGE_REPORT" | jq '.total.statements.pct')
echo "New code coverage - $NEW_COVERAGE"
if [ "$NEW_COVERAGE" != "$CURRENT_COVERAGE" ]; then
# change the README content by replacing coverage percentage
FILE_TO_COMMIT="README.md"
awk -v new_coverage="$NEW_COVERAGE" '{gsub(/!\[Coverage\]\(https:\/\/img\.shields\.io\/badge\/coverage-[0-9]{1,3}(\.[0-9]{1,4})?%25-green\)/,"![Coverage](https://img.shields.io/badge/coverage-" new_coverage "%25-green)")}1' "$FILE_TO_COMMIT" > tmp && mv tmp "$FILE_TO_COMMIT"
# commit changes to repo
SHA=$(gh api --method GET /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
-f "ref=refs/heads/$VERSION_BRANCH" \
--jq '.sha')
CONTENT=$( base64 -i $FILE_TO_COMMIT )
gh api --method PUT /repos/:owner/:repo/contents/$FILE_TO_COMMIT \
--field message="update code coverage badge" \
--field content="$CONTENT" \
--field encoding="base64" \
--field branch="$VERSION_BRANCH" \
--field sha="$SHA"
fi
- name: Generate reference tables
if: ${{ steps.tick-version.outputs.VERSION_TAG != '' }}
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
yarn generate-ref-tables
# Since package.json is modified locally from 'create-commit' step, we filter it out so we have only autogenerated doc files
# Same for README.md as it might be modified from 'code coverage badge' step.
FILES=$(git diff --name-only HEAD | grep -Ev 'package.json|README.md')
VERSION_BRANCH="${{ steps.create-commit.outputs.VERSION_BRANCH }}"
# Loop over each generated file and commit them one by one. It's not possible to commit multiple files using `gh api`
for FILENAME in $FILES; do
SHA=$(gh api --method GET /repos/:owner/:repo/contents/"$FILENAME" \
-f "ref=refs/heads/$VERSION_BRANCH" \
--jq '.sha')
CONTENT=$(base64 -i "$FILENAME")
gh api --method PUT /repos/:owner/:repo/contents/"$FILENAME" \
--field message="update $FILENAME" \
--field content="$CONTENT" \
--field encoding="base64" \
--field branch="$VERSION_BRANCH" \
--field sha="$SHA"
done
- name: Create PR
id: create-pr
if: inputs.dry-run != 'true' && ${{ steps.tick-version.outputs.VERSION_TAG != '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# at this point either new branch with new commit is created so we open PR, or we get the open PR and set the outputs
pullRequest=$(gh api --method GET "/repos/:owner/:repo/pulls" --jq ".[] | select(.head.ref == \"${{ steps.create-commit.outputs.VERSION_BRANCH }}\" and .state == \"open\") | {url: .html_url, number: .number}")
if [[ -z "$pullRequest" ]]; then
echo "No pull requests found for branch ${{ steps.create-commit.outputs.VERSION_BRANCH }}, creating new PR"
response=$(gh api --method POST /repos/:owner/:repo/pulls \
--field title="Bump version to ${{ steps.tick-version.outputs.VERSION_TAG }}" \
--field body="This PR bumps the version to ${{ steps.tick-version.outputs.VERSION_TAG }}" \
--field head="${{ steps.create-commit.outputs.VERSION_BRANCH }}" \
--field base="${{ github.base_ref || 'main' }}")
pr_url=$(echo $response | jq -r '.html_url')
pr_number=$(echo $response | jq -r '.number')
echo "pull-request-number=$pr_number" >> $GITHUB_OUTPUT
echo "pull-request-url=$pr_url" >> $GITHUB_OUTPUT
# as a last step we create a label for PR
gh api --method POST "/repos/:owner/:repo/issues/$pr_number/labels" -F "labels[]=version-bump"
else
echo "Pull requests found for branch ${{ steps.create-commit.outputs.VERSION_BRANCH }}, setting outputs"
pr_url=$(echo "$pullRequest" | jq -r '.url')
pr_number=$(echo "$pullRequest" | jq -r '.number')
#update the title and description of PR if there is new version bump commit
if [ "${{ steps.create-commit.outputs.UPDATED_VERSION_TAG }}" != "" ]; then
echo "Found new version tag ${{ steps.create-commit.outputs.UPDATED_VERSION_TAG }} for PR, updating title and description"
gh api --method PATCH /repos/:owner/:repo/pulls/$pr_number \
--field title="Bump version to ${{ steps.create-commit.outputs.UPDATED_VERSION_TAG }}" \
--field body="This PR bumps the version to ${{ steps.create-commit.outputs.UPDATED_VERSION_TAG }}"
fi
echo "pull-request-number=$pr_number" >> $GITHUB_OUTPUT
echo "pull-request-url=$pr_url" >> $GITHUB_OUTPUT
fi
post-result-to-pr:
runs-on: ubuntu-latest
if: ${{ always() }} # Always run, even if a previous step fails, since we always want to post a result message/comment
needs:
- preflight
- createVersionBumpPR
steps:
- name: Post success or failure comment to PR
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
// `needs` isn't loaded into `actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1`.context, so we have to read it from the outer context
// Using toJSON will dump a string representation of the object verbatim, which effective generates code for the `needs` javascript variable below
const needs = ${{ toJSON(needs) }}
// Note, it's possible to iterate over jobs to show multiple failures, but we instead consolidate since each job depends on the prior.
// needs["JOB"] will be undefined for skipped jobs, so spreading will show the latest failure in the chain
const { MESSAGE, JOB_NAME, VERSION_TAG, PR_URL } = {
...needs?.preflight?.outputs,
...needs?.createVersionBumpPR?.outputs,
}
// delete the coverage-pr branch if it exists
const ref = `heads/coverage-pr-${context.payload.pull_request.number}`
try {
const coverageRef = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref,
});
if (coverageRef && coverageRef.data && coverageRef.data.ref) {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref,
});
}
}catch(error) {}
// This workflow always runs on PR close, and this job always runs because of always().
// We have limited access to PR context, so check for a universally set param to determine if the PR was merged
// A skipped job is a successful job, so we can't check skipped.
if(!JOB_NAME) {
core.notice("No job name found, won't post comment to PR")
return
}
const failed = needs?.preflight?.result == 'failure' || needs?.createVersionBumpPR?.result == 'failure'
const cancelled = needs?.preflight?.result == 'cancelled' || needs?.createVersionBumpPR?.result == 'cancelled'
const cancelledMessage = `:x: ${JOB_NAME} was cancelled.`
let failureMessage = `:x: failed to run create-pr/publish workflow`
let successMessage = `:white_check_mark: successfully ran create-pr/publish workflow.`
if (JOB_NAME === 'createVersionBumpPR') {
successMessage = `:rocket: Successfully created version bump PR: ${PR_URL}`
failureMessage = `:x: failed to create version bump PR`
} else if (JOB_NAME === 'preflight') {
successMessage = `:rocket: Successfully ran preflight checks.`
failureMessage = `:x: failed to run preflight checks`
}
failureMessage = `${failureMessage}
Failure message: ${MESSAGE || "No message provided"}
Failed job: [${JOB_NAME || "Unknown"}](https://github.com/smartcontractkit/ea-framework-js/actions/runs/${{ github.run_id }})`
const body = failed ? failureMessage : cancelled ? cancelledMessage : successMessage
if(context.eventName == 'pull_request'){
core.notice("This is a pr, posting comment")
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner, // Required for Rest API route, not used for comment
repo: context.repo.repo,
body
})
}
// Regardless of whether we create a PR comment, add an error message to the job so we can see it in the Actions tab
(failed || cancelled) ? core.error(body) : core.notice(body)
github-token: ${{secrets.GITHUB_TOKEN}}