Skip to content

Instantly share code, notes, and snippets.

@airtonix
Last active June 17, 2025 10:07
Show Gist options
  • Save airtonix/3e7d7207736881506acb1acd3ec03cf0 to your computer and use it in GitHub Desktop.
Save airtonix/3e7d7207736881506acb1acd3ec03cf0 to your computer and use it in GitHub Desktop.
#!/bin/bash
# read the JSON input from stdin
inputs=$(cat);
# pick the paths_released from the JSON input and turn it into a bash array
paths_array=$(jq -r '.paths_released | fromjson | .[]' <<< "$inputs")
for release in $paths_array; do
echo "Creating release branch for $release"
data=$(
echo "$inputs" |
jq -r \
--arg release "$release" \
'to_entries
| map(
select( .key | startswith($release) )
| .key |= sub($release + "--"; "")
)
| from_entries'
)
tag_name=$(echo "$data" | jq -r '.tag_name')
branch_name="release/$tag_name"
echo "$branch_name"
git branch "$branch_name" "$tag_name"
git push origin "$branch_name"
done
#!/bin/bash
manifest_path="$1"
if [[ -z "$manifest_path" ]]; then
echo "Usage: $0 <manifest.json>"
exit 1
fi
# turn { "key": "value" } into ["key@vvalue"]
function parse_manifest_versions() {
jq -r '
to_entries
| .[]
| .key |= split("/")
| .key |= .[-1]
| "\(.key)@v\(.value)"
' <<<"$(cat)"
}
function newlines_to_array() {
jq -R -s -c 'split("\n") | map(select(length > 0))'
}
# list remote release branches that are open PRs with the label "autorelease: pending"
# return a JSON array of objects with the PR number, branch name, and labels
function get_remote_release_pr_branches() {
gh pr list --state open --json headRefName,labels |
jq -rc '
map(select( .labels | any(.name == "autorelease: pending") )) |
map({
headRefName: .headRefName,
remoteRefName: ("refs/remotes/origin/" + .headRefName),
})
'
}
# list remote release branches
# return branches names with only the last part of the refname
# e.g. refs/remotes/origin/release-foo -> release-foo
function get_remote_release_snapshot_branches() {
git for-each-ref \
--format='%(refname:lstrip=-2)' \
'refs/remotes/origin/release/*' |
jq -R -s -c '
split("\n") |
map( select(length > 0) ) |
map({
headRefName: .,
remoteRefName: ("refs/remotes/origin/" + .)
})
'
}
new_versions=$(cat "$manifest_path" | parse_manifest_versions | newlines_to_array)
conflicts=$(
jq -n \
--argjson new_versions "$new_versions" \
'{"new_versions": $new_versions, "conflicts": []}'
)
while read -r branch_data; do
if [[ -z "$branch_data" ]]; then
continue
fi
branch_name=$(echo "$branch_data" | jq -r '.headRefName')
remoteRefName=$(echo "$branch_data" | jq -r '.remoteRefName')
branch_versions=$(git show "$remoteRefName:$manifest_path" | parse_manifest_versions)
# # use jq to perform an intersection of the versions
intersection=$(
jq -n \
--argjson new_versions "$(echo "$new_versions" | jq -R -s -c 'split(" ")')" \
--argjson branch_versions "$(echo "$branch_versions" | jq -R -s -c 'split(" ")')" \
'$new_versions | map(select(. as $v | $branch_versions | index($v)))'
)
# count the number of versions in the intersection
intersection_count=$(jq length <<<"$intersection")
if [[ "$intersection_count" -eq 0 ]]; then
continue
fi
# create a JSON object with the branch name and intersection
new_conflicts=$(
jq -n \
--arg branch_name "$branch_name" \
--argjson count "$intersection_count" \
--argjson intersection "$intersection" \
'{
branch_name: $branch_name,
conflicts: $intersection,
kind: "proposed",
}'
)
# add the conflicting object to the conflicts array
conflicts=$(jq --argjson new_conflicts "$new_conflicts" '.conflicts += [$new_conflicts]' <<<"$conflicts")
done <<<"$(jq -c '.[]' <<<"$(get_remote_release_pr_branches)")"
while read -r branch_data; do
if [[ -z "$branch_data" ]]; then
continue
fi
branch_ref=$(echo "$branch_data" | jq -r '.remoteRefName')
branch_name=$(echo "$branch_data" | jq -r '.headRefName')
branch_versions=$(git show "$branch_ref:$manifest_path" | parse_manifest_versions | newlines_to_array)
# use jq to perform an intersection of the versions
intersection=$(
jq -n \
--argjson new_versions "${new_versions}" \
--argjson branch_versions "${branch_versions}" \
'$new_versions | map(select(. as $v | $branch_versions | index($v)))'
)
# count the number of versions in the intersection
intersection_count=$(jq length <<<"$intersection")
if [[ "$intersection_count" -eq 0 ]]; then
continue
fi
# create a JSON object with the branch name and intersection
new_conflicts=$(
jq -n \
--arg branch_name "$branch_name" \
--argjson count "$intersection_count" \
--argjson intersection "$intersection" \
'{
branch_name: $branch_name,
conflicts: $intersection,
kind: "released",
}'
)
# add the conflicting object to the conflicts array
conflicts=$(jq --argjson new_conflicts "$new_conflicts" '.conflicts += [$new_conflicts]' <<<"$conflicts")
done <<<"$(jq -c '.[]' <<<"$(get_remote_release_snapshot_branches)")"
echo "$conflicts" | jq -c
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/refs/heads/main/schemas/config.json",
"versioning": "always-bump-patch",
"include-component-in-tag": true,
"include-v-in-tag": true,
"tag-separator": "@",
"separate-pull-requests": true,
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"release-type": "node",
"packages": {
"apps/one": {},
"apps/two": {},
"pkgs/ui": {}
}
}
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/refs/heads/main/schemas/config.json",
"versioning": "always-bump-minor",
"include-component-in-tag": true,
"include-v-in-tag": true,
"tag-separator": "@",
"separate-pull-requests": true,
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"always-update": true,
"always-link-local": true,
"initial-version": "0.0.1",
"release-type": "node",
"packages": {
"apps/one": {},
"apps/two": {},
"pkgs/ui": {}
}
}
name: Pr
on:
pull_request:
branches:
- main
- release/*
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
permissions:
actions: read
contents: write
issues: write
deployments: write
pull-requests: write
id-token: write
jobs:
ReleaseConflictCheck:
runs-on: ubuntu-latest
# is this a release please PR? test the labels
# release please creates labels for release prs like `pending-autorelease`
# if this isn't a release please PR, we don't need to do anything
if: contains(github.event.pull_request.labels.*.name, 'pending-autorelease')
steps:
# release please updates the release-please-manifest.json file with it's proposed versions
# find that and check if the version is already in use
# the file lookws like :
# {
# "apps/one": "0.1.0",
# "apps/two": "0.1.0",
# }
#
# the tags we're looking for look like this:
# - [email protected]
# - [email protected]
#
# so we can use the jq command to extract the values and then check if the tag exists
# format the output of jq to be the tag we're looking for
- name: Check for release conflicts
run: |
- name: Check conflicts with existing releases
shell: bash
run: |
script=".github/scripts/find-release-tag-conflict.sh"
conflicts="$($script .github/release-please-manifest.json)"
conflicts_count=$(echo "$conflicts" | jq -s '.conflicts[] | length')
released_conflicts="$(echo "$conflicts" | jq -c '.conflicts[] | select(.kind == "released")')"
released_conflicts_count=$(echo "$released_conflicts" | jq -s 'length')
proposed_conflicts="$(echo "$conflicts" | jq -c '.conflicts[] | select(.kind == "proposed")')"
proposed_conflicts_count=$(echo "$proposed_conflicts" | jq -s 'length')
echo "conflicts=${conflicts}" >> $GITHUB_OUTPUT
echo "conflicts_count=${conflicts_count}" >> $GITHUB_OUTPUT
echo "released_conflicts=${released_conflicts}" >> $GITHUB_OUTPUT
echo "released_conflicts_count=${released_conflicts_count}" >> $GITHUB_OUTPUT
echo "proposed_conflicts=${proposed_conflicts}" >> $GITHUB_OUTPUT
echo "proposed_conflicts_count=${proposed_conflicts_count}" >> $GITHUB_OUTPUT
if [ "$conflicts_count" -gt 0 ]; then
echo "Release conflicts found"
echo "$conflicts" | jq -r
exit 1
fi
echo "No release conflicts found, proceeding with PR"
YourOtherPrCheckJob:
runs-on: ubuntu-latest
steps:
- run: |
echo true
name: Release
on:
push:
branches:
# for prod releases
- main
# for hotfixes
- release/*
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
permissions:
actions: read
contents: write
issues: write
deployments: write
pull-requests: write
id-token: write
jobs:
Process:
runs-on: ubuntu-latest
env:
RELEASE_BOT_NAME: "Release Bot"
RELEASE_BOT_EMAIL: "[email protected]"
outputs:
releases_created: ${{ steps.release-please.outputs.releases_created }}
prs_created: ${{ steps.release-please.outputs.prs_created }}
prs: ${{ steps.release-please.outputs.prs }}
pr: ${{ steps.release-please.outputs.pr }}
sha: ${{ steps.release-please.outputs.sha }}
steps:
- id: config_filename
env:
DEFAULT_RELEASE_CONFIG_FILE: release-please-config.json
HOTFIX_RELEASE_CONFIG_FILE: release-please-config.hotfix.json
run: |
case "${{ github.ref_name }}" in
release/*)
echo "config_filename=${HOTFIX_RELEASE_CONFIG_FILE}" >> $GITHUB_OUTPUT
;;
*)
echo "config_filename=${DEFAULT_RELEASE_CONFIG_FILE}" >> $GITHUB_OUTPUT
;;
esac
- uses: googleapis/release-please-action@v4
id: release-please
with:
token: "${{ secrets.RELEASE_PLEASE_TOKEN }}"
config-file: ".github/${{ steps.config_filename.outputs.config_filename }}"
manifest-file: ".github/release-please-manifest.json"
target-branch: "${{ github.ref_name }}"
- name: Print Release Data
run: |
echo 'Release Data:'
echo '''
${{ toJSON(steps.release-please.outputs) }}
'''
- name: Checkout Code
if: ${{ steps.release-please.outputs.releases_created != 'false' }}
uses: actions/checkout@v4
with:
fetch-depth: 10
fetch-tags: true
ref: ${{ github.ref_name }}
persist-credentials: true
- name: Create Release Snapshot Branches
if: ${{ steps.release-please.outputs.releases_created != 'false' }}
run: |
git config --global user.name "$RELEASE_BOT_NAME"
git config --global user.email "$RELEASE_BOT_EMAIL"
echo "$(jq -cr <<< '${{ toJSON(steps.release-please.outputs) }}')" |
.github/scripts/create-release-branch.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment