Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds scripts and configurations to support hotfix release workflows #2545

Merged
merged 6 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
push:
branches:
- main
- release
- hotfix/**

jobs:
build-and-publish:
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
- name: Publish
run: |
yarn lerna publish from-package --yes --allowBranch=main --loglevel=verbose --dist-tag latest
yarn lerna publish from-package --yes --loglevel=verbose --dist-tag latest
release:
needs: build-and-publish # comment when testing locally with https://github.com/nektos/act
Expand All @@ -68,7 +68,7 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Generate Release Tag
- name: Generate Tags
id: get-release-tag
run: ./scripts/generate-release-tags.sh

Expand All @@ -81,3 +81,22 @@ jobs:
script: |
const script = require('./scripts/github-action/create-github-release.js')
await script({github, context, core, exec})
# When hotfix is merged back to main, we tag all the packages that were changed in the hotfix commit.
tag-hotfix:
if: startsWith(github.event.head_commit.message, 'Hotfix:') == true && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Generate Package Specific Tags
id: get-release-tag
run: ./scripts/generate-package-tags.sh
12 changes: 7 additions & 5 deletions .github/workflows/version-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ on:
description: 'Base branch to create PR to'
required: true
default: 'main'
type: choice
options:
- main
- release
run_id:
description: 'Unique identifier for the run'
required: false
Expand Down Expand Up @@ -67,7 +63,13 @@ jobs:
run: NODE_ENV=production yarn build

- name: Version Packages
run: yarn lerna version minor --yes --allow-branch ${{ github.event.inputs.branch }} --no-git-tag-version --no-commit-hooks --no-private
run: |
# minor version bump for main branch and patch for hotfixes
if [ "${{ github.event.inputs.base_branch }}" == "main" ]; then
yarn lerna version minor --yes --allow-branch ${{ github.event.inputs.branch }} --no-git-tag-version --no-commit-hooks --no-private
else
yarn lerna version patch --yes --allow-branch ${{ github.event.inputs.branch }} --no-git-tag-version --no-commit-hooks --no-private
fi
- name: Commit and push
id: commit_and_push
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"npmClientArgs": ["--ignore-engines", "--ignore-optional"]
},
"version": {
"allowBranch": ["main", "release"]
"allowBranch": ["main", "hotfix/*"]
}
}
}
14 changes: 14 additions & 0 deletions scripts/generate-package-tags.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

#!/bin/bash

set -e # exit on error

# this script assumes the last commit was publish commit and gets all package.json files changed in the last commit
# and generates tags for each package.json file.
files=$(git show --pretty="" --name-only HEAD | grep -Ei '^packages/.*package\.json$')
for file in $files; do
tag=$(cat $file | jq -r '.name + "@" + .version')
echo "Tagging $sha with $tag"
git tag -a $tag -m "Release $tag" --force
git push origin $tag
done
36 changes: 24 additions & 12 deletions scripts/generate-release-tags.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ sha=$(git rev-parse HEAD);
branch=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD);
message=$(git log -1 --pretty=%B);

if [[ $branch != "main" && $branch != "release" ]];
# Only generate release tags for main branch and hotfix branches.
if [[ $branch != "main" && ! $branch == hotfix/* ]];
then
echo "Skipping release tag generation as branch is not main or release"
echo "Skipping release tag generation as branch is not main or hotfix"
exit 0
fi;

Expand All @@ -26,6 +27,14 @@ then
exit 1
fi

# If branch is hotfix, prefix should be hotfix. If not, it should be release.
if [[ $branch == hotfix/* ]];
then
prefix="hotfix"
else
prefix="release"
fi

# Generate and push release tag. Release tag format: release-YYYY-MM-DD[.N] e.g. release-2024-01-01
if ! n=$(git rev-list --count $sha~ --grep "Publish" --since="00:00"); then
echo 'Failed to compute release tag. Exiting.'
Expand All @@ -36,18 +45,21 @@ else
*) suffix=".$n" ;; # subsequent commits get a suffix, starting with .1
esac

tag=$(printf release-$(date '+%Y-%m-%d%%s') $suffix)
tag=$(printf $prefix-$(date '+%Y-%m-%d%%s') $suffix)
echo "Tagging $sha with $tag"
git tag -a $tag -m "Release $tag" --force
git push origin $tag
fi

# this script assumes the last commit was publish commit and gets all package.json files changed in the last commit
# and generates tags for each package.json file.
files=$(git show --pretty="" --name-only HEAD | grep -Ei '^packages/.*package\.json$')
for file in $files; do
tag=$(cat $file | jq -r '.name + "@" + .version')
echo "Tagging $sha with $tag"
git tag -a $tag -m "Release $tag" --force
git push origin $tag
done

# For hotfix, we don't tag each package version and we do it when hotfix changes are merged to main branch.
if [[ $branch == hotfix/* ]];
then
echo "Skipping package tag generation for hotfix branch"
exit 0
fi;

# Generate and push package tags.
curr_path=$(pwd)
dir_name=$(dirname $0)
./$dir_name/generate-package-tags.sh
38 changes: 27 additions & 11 deletions scripts/github-action/create-github-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = async ({ github, context, core, exec }) => {
const latestReleaseTag = latestRelease ? latestRelease.data.tag_name : null

// Extract package tags that are published in the current release by lerna version
const packageTags = await extractPackageTags(GITHUB_SHA, exec, core)
const packageTags = await extractPackageNames(GITHUB_SHA, exec, core)
const tagsContext = { currentRelease: newReleaseTag, prevRelease: latestReleaseTag, packageTags }
const changeLog = formatChangeLog(prs, tagsContext, context)

Expand Down Expand Up @@ -81,7 +81,8 @@ async function getReleaseTag(core, exec) {
'describe',
'--abbrev=0',
'--tags',
'--match=release-*'
'--match=release-*',
'--match=hotfix-*'
])
if (exitCode !== 0) {
// if the release tag is not found, then we cannot proceed further
Expand All @@ -90,18 +91,33 @@ async function getReleaseTag(core, exec) {
return stdout.trim()
}

// Extract package tags that are published in the current release by lerna version
async function extractPackageTags(sha, exec, core) {
const { stdout, stderr, exitCode } = await exec.getExecOutput('git', ['tag', '--points-at', sha])
// Extract packages published in the current release
async function extractPackageNames(sha, exec, core) {
const { stdout, stderr, exitCode } = await exec.getExecOutput('git', [
'diff-tree',
'--no-commit-id',
'--name-only',
sha,
'-r'
])
if (exitCode !== 0) {
// if the package tags are not found, then we cannot proceed further
core.error(`Failed to extract package tags: ${stderr}`)
}
// filter out only the tags that are related to segment packages
return stdout
.split('\n')
.filter(Boolean)
.filter((tag) => tag.includes('@segment/') && !tag.includes('staging'))
// filter out files that are not package.json
const files = stdout.split('\n').filter((file) => file.startsWith('packages/') && file.endsWith('package.json'))
// get the package versions and names from package.json files
const packageTags = await Promise.all(
files.map(async (file) => {
const { stdout, stderr, exitCode } = await exec.getExecOutput('cat', [file])
if (exitCode !== 0) {
core.error(`Failed to extract package tags: ${stderr}`)
}
const pkg = JSON.parse(stdout)
return `${pkg.name}@${pkg.version}`
})
)
return packageTags
}

// Get PRs between two commits
Expand Down Expand Up @@ -227,7 +243,7 @@ function formatTable(prs, tableConfig, title = '') {
`
}
/*
* Map PR with affected destinations
* Map PR with affected destinations
*/
function mapPRWithAffectedDestinations(pr) {
let affectedDestinations = []
Expand Down
Loading