Skip to content

Update Node.js to v22.12.0 #299

Update Node.js to v22.12.0

Update Node.js to v22.12.0 #299

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}}