Skip to content

Create new release diffs #457

Create new release diffs

Create new release diffs #457

Workflow file for this run

name: Create new release diffs
on:
workflow_dispatch:
inputs:
releaseVersion:
description: "Backstage release version to process (e.g. 1.48.0). If omitted, auto-detects all untracked releases."
required: false
default: ""
force:
description: "Re-process already-tracked releases (useful for backfilling missing diffs). Only applies when no releaseVersion is specified."
type: boolean
required: false
default: false
jobs:
detect:
name: Detect new releases
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.detect.outputs.versions }}
steps:
- name: Detect new @backstage/create-app versions
id: detect
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
resolve_manifest() {
local RELEASE_VERSION="$1"
MANIFEST=$(curl -sf "https://raw.githubusercontent.com/backstage/versions/main/v1/releases/${RELEASE_VERSION}/manifest.json") || {
echo "Error: failed to fetch manifest for $RELEASE_VERSION (HTTP error)" >&2
exit 1
}
CREATE_APP_VERSION=$(echo "$MANIFEST" | jq -r '.packages[] | select(.name == "@backstage/create-app") | .version')
if [ -z "$CREATE_APP_VERSION" ] || [ "$CREATE_APP_VERSION" = "null" ]; then
echo "Error: no @backstage/create-app found in manifest for $RELEASE_VERSION" >&2
exit 1
fi
echo "$CREATE_APP_VERSION"
}
VERSIONS_JSON="[]"
if [ -n "${{ github.event.inputs.releaseVersion }}" ]; then
RELEASE_VERSION="${{ github.event.inputs.releaseVersion }}"
RELEASE_VERSION="${RELEASE_VERSION#v}"
CREATE_APP_VERSION=$(resolve_manifest "$RELEASE_VERSION")
echo "Forced version: createApp=$CREATE_APP_VERSION releaseVersion=$RELEASE_VERSION"
VERSIONS_JSON=$(echo "$VERSIONS_JSON" | jq -c \
--arg v "$CREATE_APP_VERSION" \
--arg rv "$RELEASE_VERSION" \
'. + [{"version": $v, "releaseVersion": $rv}]')
else
KNOWN_RELEASES=$(curl -sf https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/releases.json | \
jq -r 'keys[]')
while IFS= read -r TAG; do
RELEASE_VERSION="${TAG#v}"
if echo "$KNOWN_RELEASES" | grep -qx "$RELEASE_VERSION"; then
if [ "${{ github.event.inputs.force }}" != "true" ]; then
echo "Release $RELEASE_VERSION already tracked, skipping"
continue
fi
echo "Release $RELEASE_VERSION already tracked, re-processing (force=true)"
fi
CREATE_APP_VERSION=$(resolve_manifest "$RELEASE_VERSION") || continue
echo "Found new version: createApp=$CREATE_APP_VERSION releaseVersion=$RELEASE_VERSION"
VERSIONS_JSON=$(echo "$VERSIONS_JSON" | jq -c \
--arg v "$CREATE_APP_VERSION" \
--arg rv "$RELEASE_VERSION" \
'. + [{"version": $v, "releaseVersion": $rv}]')
done < <(gh api "/repos/backstage/backstage/releases?per_page=50" | jq -r 'reverse | .[].tag_name')
fi
echo "versions=$VERSIONS_JSON" >> $GITHUB_OUTPUT
process:
name: Process new releases
needs: detect
if: needs.detect.outputs.versions != '[]'
runs-on: ubuntu-latest
env:
BACKSTAGE_APP_NAME: backstagediffapp
steps:
- run: echo "πŸŽ‰ The job was automatically triggered by a ${{ github.event_name }} event."
- name: Check out repository
uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup git bot user
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Process all new versions
env:
VERSIONS: ${{ needs.detect.outputs.versions }}
run: |
set -e
while IFS= read -r ENTRY; do
VERSION=$(echo "$ENTRY" | jq -r '.version')
RELEASE_VERSION=$(echo "$ENTRY" | jq -r '.releaseVersion')
echo "=== Processing createApp=$VERSION releaseVersion=$RELEASE_VERSION ==="
# ── Release branch ──────────────────────────────────────────────
if [ -z "$(git ls-remote --heads origin release/${VERSION})" ]; then
git fetch origin release-base
git checkout -fB release-base origin/release-base
git clean -fd
printf "$BACKSTAGE_APP_NAME\n\t" | npx @backstage/create-app@${VERSION} --skip-install
rm -rf "$(pwd)/$BACKSTAGE_APP_NAME/.git"
cp -ar "$(pwd)/$BACKSTAGE_APP_NAME/." "$(pwd)"
git checkout -b release/${VERSION}
git add .
git reset -- $BACKSTAGE_APP_NAME
git commit -m "Release ${VERSION}"
git push origin release/${VERSION}
else
echo "Branch release/${VERSION} already exists, skipping"
fi
# ── Yarn plugin branch ──────────────────────────────────────────
if [ -z "$(git ls-remote --heads origin release/yarn-plugin/${RELEASE_VERSION})" ]; then
git fetch origin "release/${VERSION}"
git checkout -fB "release/${VERSION}" "origin/release/${VERSION}"
git clean -fd
yarn plugin import https://versions.backstage.io/v1/tags/next/yarn-plugin
YARN_ENABLE_IMMUTABLE_INSTALLS=false npx --legacy-peer-deps -p @backstage/cli@latest backstage-cli versions:bump --release $RELEASE_VERSION
git checkout -b release/yarn-plugin/${RELEASE_VERSION}
git add .
git reset -- $BACKSTAGE_APP_NAME yarn.lock
git commit -m "Release ${RELEASE_VERSION}"
git push origin release/yarn-plugin/${RELEASE_VERSION}
else
echo "Branch release/yarn-plugin/${RELEASE_VERSION} already exists, skipping"
fi
# ── Legacy diffs ─────────────────────────────────────────────────
git fetch origin release-diff-base
git checkout -fB release-diff-base origin/release-diff-base
git clean -fd
if [ -z "$(git ls-remote --heads origin release-diff/legacy/v${VERSION})" ]; then
git checkout -b release-diff/legacy/v${VERSION}
mkdir diffs
else
git fetch origin "release-diff/legacy/v${VERSION}"
git checkout -fB "release-diff/legacy/v${VERSION}" "origin/release-diff/legacy/v${VERSION}"
git clean -fd
fi
git fetch origin release/$VERSION
for ver in $(curl -sf https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/releases.json | jq -r 'to_entries | .[] | .value.createApp // .key'); do
[ "$(printf '%s\n%s\n' "$ver" "$VERSION" | sort -V | head -1)" != "$ver" ] && continue
DIFF_FILE="diffs/$ver..$VERSION.diff"
if [ ! -f "$DIFF_FILE" ]; then
echo "Creating diff between $ver and $VERSION"
git fetch origin release/$ver
git diff -U1 origin/release/$ver..origin/release/$VERSION > "$DIFF_FILE"
git add "$DIFF_FILE"
fi
done
if ! git diff --cached --quiet; then
git commit -m "Add release ${VERSION}"
git push origin release-diff/legacy/v${VERSION}
else
echo "No new diffs for release-diff/legacy/v${VERSION}"
fi
# ── Backfill legacy diff branches ─────────────────────────────────
git fetch origin "release/${VERSION}"
while IFS= read -r future_ver; do
[ "$future_ver" = "$VERSION" ] && continue
OLDER=$(printf '%s\n%s\n' "$VERSION" "$future_ver" | sort -V | head -1)
[ "$OLDER" != "$VERSION" ] && continue
FUTURE_BRANCH="release-diff/legacy/v${future_ver}"
[ -z "$(git ls-remote --heads origin "${FUTURE_BRANCH}")" ] && continue
git fetch origin "${FUTURE_BRANCH}"
git checkout -fB "${FUTURE_BRANCH}" "origin/${FUTURE_BRANCH}"
git clean -fd
DIFF_FILE="diffs/${VERSION}..${future_ver}.diff"
if [ ! -f "$DIFF_FILE" ]; then
echo "Backfilling legacy diff ${VERSION}..${future_ver}"
git fetch origin "release/${future_ver}"
git diff -U1 "origin/release/${VERSION}..origin/release/${future_ver}" > "$DIFF_FILE"
git add "$DIFF_FILE"
if ! git diff --cached --quiet; then
git commit -m "Backfill diff ${VERSION}..${future_ver}"
git push origin "${FUTURE_BRANCH}"
else
echo "No changes to commit for ${DIFF_FILE}, skipping"
fi
fi
done < <(curl -sf https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/releases.json | jq -r '[to_entries[] | .value.createApp // .key] | unique[]')
# ── Yarn plugin diffs ────────────────────────────────────────────
git fetch origin release-diff-base
git checkout -fB release-diff-base origin/release-diff-base
git clean -fd
if [ -z "$(git ls-remote --heads origin release-diff/v${RELEASE_VERSION})" ]; then
git checkout -b release-diff/v${RELEASE_VERSION}
mkdir diffs
else
git fetch origin "release-diff/v${RELEASE_VERSION}"
git checkout -fB "release-diff/v${RELEASE_VERSION}" "origin/release-diff/v${RELEASE_VERSION}"
git clean -fd
fi
git fetch origin release/yarn-plugin/$RELEASE_VERSION
for ver in $(curl -sf https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/releases-yarn-plugin.json | jq -r 'to_entries | .[] | .key'); do
[ "$(printf '%s\n%s\n' "$ver" "$RELEASE_VERSION" | sort -V | head -1)" != "$ver" ] && continue
DIFF_FILE="diffs/$ver..$RELEASE_VERSION.diff"
if [ ! -f "$DIFF_FILE" ]; then
echo "Creating yarn-plugin diff between $ver and $RELEASE_VERSION"
git fetch origin release/yarn-plugin/$ver
git diff -U1 origin/release/yarn-plugin/$ver..origin/release/yarn-plugin/$RELEASE_VERSION > "$DIFF_FILE"
git add "$DIFF_FILE"
fi
done
if ! git diff --cached --quiet; then
git commit -m "Add release ${RELEASE_VERSION}"
git push origin release-diff/v${RELEASE_VERSION}
else
echo "No new diffs for release-diff/v${RELEASE_VERSION}"
fi
# ── Backfill yarn-plugin diff branches ────────────────────────────
git fetch origin "release/yarn-plugin/${RELEASE_VERSION}"
while IFS= read -r future_rv; do
[ "$future_rv" = "$RELEASE_VERSION" ] && continue
OLDER=$(printf '%s\n%s\n' "$RELEASE_VERSION" "$future_rv" | sort -V | head -1)
[ "$OLDER" != "$RELEASE_VERSION" ] && continue
FUTURE_BRANCH="release-diff/v${future_rv}"
[ -z "$(git ls-remote --heads origin "${FUTURE_BRANCH}")" ] && continue
git fetch origin "${FUTURE_BRANCH}"
git checkout -fB "${FUTURE_BRANCH}" "origin/${FUTURE_BRANCH}"
git clean -fd
DIFF_FILE="diffs/${RELEASE_VERSION}..${future_rv}.diff"
if [ ! -f "$DIFF_FILE" ]; then
echo "Backfilling yarn-plugin diff ${RELEASE_VERSION}..${future_rv}"
git fetch origin "release/yarn-plugin/${future_rv}"
git diff -U1 "origin/release/yarn-plugin/${RELEASE_VERSION}..origin/release/yarn-plugin/${future_rv}" > "$DIFF_FILE"
git add "$DIFF_FILE"
if ! git diff --cached --quiet; then
git commit -m "Backfill diff ${RELEASE_VERSION}..${future_rv}"
git push origin "${FUTURE_BRANCH}"
else
echo "No changes to commit for ${DIFF_FILE}, skipping"
fi
fi
done < <(curl -sf https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/releases-yarn-plugin.json | jq -r 'keys[]')
# ── Update JSON files on master ──────────────────────────────────
git fetch origin master
git checkout -fB master origin/master
git clean -fd
jq ".\"$RELEASE_VERSION\" = { createApp: \"$VERSION\" }" < releases.json > _releases.json
mv _releases.json releases.json
jq ".\"$RELEASE_VERSION\" = { createApp: \"$VERSION\" }" < releases-yarn-plugin.json > _releases-yarn-plugin.json
mv _releases-yarn-plugin.json releases-yarn-plugin.json
git add releases.json releases-yarn-plugin.json
if ! git diff --cached --quiet; then
git commit -m "Add release ${VERSION}"
git push origin master
else
echo "releases.json already up to date, skipping"
fi
done <<< "$(echo "$VERSIONS" | jq -c '.[]')"
- run: echo "🍏 This job's status is ${{ job.status }}."