Skip to content
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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Changelog

All notable changes to this project will be documented in this file.
> [!NOTE]
> This file is no longer maintained. Starting June 17, 2026, Blueprint uses GitHub auto-generated release notes for new releases.
> See [RELEASING.md](RELEASING.md) for the current release process and [GitHub Releases](https://github.com/square/Blueprint/releases) for current release notes.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Historical release notes are preserved below.

## [Main]

Expand Down
50 changes: 38 additions & 12 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
# Releasing a new version

1. Prepare a release by updating the changelog.
Blueprint no longer maintains `CHANGELOG.md` for new releases. Release notes are generated by GitHub from merged pull requests and commits.

1. Make sure you're on the `main` branch, and `git pull` to get the latest commits.
## Prepare the release

1. Create a branch off `main`.
1. Choose the next semantic version tag, using the existing bare version format, for example `6.8.0`.

1. Update `CHANGELOG.md` (in the root of the repo), moving current changes under `Main` to a new section for the version you are releasing.

The changelog uses [reference links](https://daringfireball.net/projects/markdown/syntax#link) to link each version's changes. Remember to add a link to the new version at the bottom of the file, and to update the link to `[main]`.
1. Make sure all release-bound changes are merged to `main`.

1. Push your branch and open a PR into `main`.
1. Confirm the latest `main` branch build is passing in [GitHub Actions](https://github.com/square/Blueprint/actions).

1. Go to the [Releases](https://github.com/square/Blueprint/releases) and `Draft a new release`.
1. Do not add entries to `CHANGELOG.md`. The file is retained only for historical release notes.

1. `Choose a tag` and create a tag for the new version.
## Cut the release

1. In the release notes, copy the changes from the changelog.
The release script creates a draft GitHub release for the requested version and asks GitHub to generate the release notes.

1. Ensure the `Title` corresponds to the version we're publishing.
```sh
git checkout main
git pull --ff-only origin main
Scripts/release.sh --version 6.8.0
```

1. Hit `Publish release`.
By default, the script targets `origin/main` and creates a draft release. Review the generated release notes in GitHub, make any necessary edits, then publish the release. If you intentionally want to skip the draft review step, pass `--publish`.

To target a specific merge commit instead of `origin/main`, pass its full SHA:

```sh
Scripts/release.sh --version 6.8.0 --target 0123456789abcdef0123456789abcdef01234567
```

## Manual fallback

1. Go to [Releases](https://github.com/square/Blueprint/releases) and choose `Draft a new release`.

1. Choose or create the tag for the new version. Target the latest `main` commit that should be included in the release.

1. Use GitHub's generated release notes.

1. Set the release title to the version number, review the generated notes, and publish the release.

## After publishing

Fetch tags locally if you need the new release tag in your checkout:

```sh
git fetch --tags origin
```
231 changes: 155 additions & 76 deletions Scripts/release.sh
Original file line number Diff line number Diff line change
@@ -1,111 +1,190 @@
#!/bin/bash

echo "TODO: Update this script to use the new release process."
echo "Use the manual steps in RELEASING.md for now."
exit 0
#!/usr/bin/env bash

set -euo pipefail

branch="main"
diff_check=false
target="origin/main"
draft=true
open_release=true
dry_run=false
fail_on_no_commits=true
prerelease=false
notes_start_tag=""

# Function to display usage
usage() {
echo "Usage: $0 --version <version> [--branch <branch>] [--no-diff-check]"
exit 1
cat <<'END'
Usage: Scripts/release.sh --version <version> [options]

Creates a GitHub release with auto-generated release notes.

Options:
-v, --version <version> Version tag to release, for example 6.8.0.
-t, --target <ref-or-sha> Git ref or commit SHA to release. Defaults to origin/main.
--notes-start-tag <tag> Tag to start generated release notes from.
--publish Publish immediately instead of creating a draft.
--draft Create a draft release. This is the default.
--prerelease Mark the release as a prerelease.
--allow-no-commits Allow a release with no commits since the previous release.
--no-open Do not open the release in a browser.
--dry-run Print the gh command without creating the release.
-h, --help Show this help text.
END
}

# Check if gh CLI is installed
if ! command -v gh &> /dev/null; then
echo "Error: GitHub CLI (gh) is not installed. It is required by this script."
echo "Please install it from https://cli.github.com/, authenticate, and try again."
exit 1
fi
require_value() {
if [[ $# -lt 2 || -z "${2:-}" ]]; then
echo "Error: $1 requires a value." >&2
usage
exit 1
fi

if [[ "${2:0:1}" == "-" ]]; then
echo "Error: $1 requires a value." >&2
usage
exit 1
fi
}

# Parse options
while [[ $# -gt 0 ]]; do
case $1 in
-v|--version) version="$2"; shift 2 ;;
-b|--branch) branch="$2"; shift 2 ;;
-n|--no-diff-check) diff_check=false; shift ;;
--) shift; break ;;
-*|--*) echo "Unknown option $1"; usage ;;
*) break ;;
case "$1" in
-v|--version)
require_value "$@"
version="${2:-}"
shift 2
;;
-t|--target)
require_value "$@"
target="${2:-}"
shift 2
;;
--notes-start-tag)
require_value "$@"
notes_start_tag="${2:-}"
shift 2
;;
--publish)
draft=false
shift
;;
--draft)
draft=true
shift
;;
--prerelease)
prerelease=true
shift
;;
--allow-no-commits)
fail_on_no_commits=false
shift
;;
--no-open)
open_release=false
shift
;;
--dry-run)
dry_run=true
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-*|--*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
*)
echo "Unexpected argument: $1" >&2
usage
exit 1
;;
esac
done

# Check if version argument is provided
if [ -z "${version:-}" ]; then
echo "Error: You must provide a version number."
if [[ -z "${version:-}" ]]; then
echo "Error: You must provide a version number." >&2
usage
exit 1
fi

# Ensure there are no unstaged changes
if [ "$diff_check" = true ] && ! git diff --quiet origin/"$branch"; then
echo "Error: This branch has differences compared to origin/$branch. Please push or undo these changes before continuing."
echo "You can bypass this check with the --no-diff-check flag."
if [[ -z "$target" ]]; then
echo "Error: --target cannot be empty." >&2
exit 1
fi

# This timestamp is used during branch creation.
# It's helpful in cases where the script fails and a new branch needs to
# be created on a subsequent attempt.
timestamp=$(date +"%Y-%m-%d-%H_%M_%S")

git checkout "$branch"
git pull
if ! command -v gh > /dev/null; then
echo "Error: GitHub CLI (gh) is required. Install it from https://cli.github.com/ and authenticate before retrying." >&2
exit 1
fi

# Create a new branch with the version and timestamp
branch_name="$(whoami)/release-$version-$timestamp"
git checkout -b "$branch_name"
if ! gh auth status > /dev/null 2>&1; then
echo "Error: GitHub CLI (gh) is not authenticated. Run 'gh auth login' before retrying." >&2
exit 1
fi

# Define the git repo root
repo_root=$(git rev-parse --show-toplevel)
cd "$repo_root"

# Extract the previous version number from version.rb
previous_version=$(grep 'BLUEPRINT_VERSION' "$repo_root/version.rb" | awk -F"'" '{print $2}')
if [[ "$dry_run" == false ]] && { ! git diff --quiet || ! git diff --cached --quiet; }; then
echo "Error: The working tree has uncommitted changes. Commit or stash them before cutting a release." >&2
exit 1
fi

# Update the library version in version.rb
sed -i '' "s/BLUEPRINT_VERSION ||= .*/BLUEPRINT_VERSION ||= '$version'/" "$repo_root/version.rb"
git fetch origin --tags

# Update CHANGELOG.md using stamp-changelog.sh
"$repo_root/Scripts/stamp-changelog.sh" --version "$version" --previous-version "$previous_version"
target_sha=$(git rev-parse --verify "$target^{commit}")

# Change directory into the SampleApp dir and update Podfile.lock using a subshell
(
cd "$repo_root/SampleApp"
bundle exec pod install
if gh release view "$version" > /dev/null 2>&1; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Suggestion: Could we also guard against an existing tag for this version before creating the release? gh release create "$version" --target "$target_sha" only uses --target when it auto-creates the tag. If the tag already exists but no release exists yet, this can create the release from that existing tag even though the script printed a different target SHA.

One option:

existing_tag_sha=$(git rev-parse --verify --quiet "$version^{commit}" || true)
if [[ -n "$existing_tag_sha" && "$existing_tag_sha" != "$target_sha" ]]; then
  echo "Error: Tag $version already exists at $existing_tag_sha, not $target_sha." >&2
  exit 1
fi

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #623 to address this. The new guard checks an existing version tag before release creation and fails if it resolves to a different commit than the requested target.

echo "Error: A GitHub release already exists for $version." >&2
exit 1
fi

release_command=(
gh release create "$version"
--target "$target_sha"
--title "$version"
--generate-notes
)

# Commit the changes
git add .
git commit -m "Bumping versions to $version."
if [[ "$draft" == true ]]; then
release_command+=(--draft)
fi

# Push the branch and open a PR into main
git push origin "$branch_name"
if [[ "$prerelease" == true ]]; then
release_command+=(--prerelease)
fi

pr_body=$(cat <<-END
https://github.com/square/Blueprint/blob/main/CHANGELOG.md
if [[ "$fail_on_no_commits" == true ]]; then
release_command+=(--fail-on-no-commits)
fi

Post-merge steps:
if [[ -n "$notes_start_tag" ]]; then
release_command+=(--notes-start-tag "$notes_start_tag")
fi

- Once the PR is merged, fetch changes and tag the release, using the merge commit:
\`\`\`
git fetch
git tag $version <merge commit SHA>
git push origin $version
\`\`\`
release_kind=""
if [[ "$draft" == true ]]; then
release_kind="draft "
fi

- Publish to CocoaPods:
\`\`\`
bundle exec pod trunk push BlueprintUI.podspec
bundle exec pod trunk push --synchronous BlueprintUICommonControls.podspec
\`\`\`
END
)
printf 'Creating %srelease %s from %s (%s).\n' "$release_kind" "$version" "$target" "$target_sha"

gh pr create --draft --title "release: Blueprint $version" --body "$pr_body"
if [[ "$dry_run" == true ]]; then
printf 'Dry run:'
printf ' %q' "${release_command[@]}"
printf '\n'
exit 0
fi

"${release_command[@]}"

gh pr view --web
if [[ "$open_release" == true ]]; then
gh release view "$version" --web
fi

echo "Branch $branch_name created and pushed. A draft PR has been created."
echo "Release $version created. Review the generated notes in GitHub before publishing if this is a draft."
Loading
Loading