Skip to content
Draft
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
16 changes: 11 additions & 5 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,15 @@ steps:

- label: ':s3: Publish XCFramework to S3'
depends_on: build-xcframework
if: build.pull_request.id == null
# The `:rocket: Publish Swift release` step handles uploads when
# `NEW_VERSION` is set, so this step only covers per-commit trunk
# uploads keyed by the commit SHA.
if: build.pull_request.id == null && build.env("NEW_VERSION") == null
command: |
buildkite-agent artifact download '*.xcframework.zip' .
buildkite-agent artifact download '*.xcframework.zip.checksum.txt' .
install_gems
# Version precedence: explicit `NEW_VERSION` override wins, then a
# tag build publishes under the tag, otherwise fall back to the
# commit SHA so every push gets a stable artifact URL.
bundle exec fastlane publish_to_s3 version:${NEW_VERSION:-${BUILDKITE_TAG:-$BUILDKITE_COMMIT}}
bundle exec fastlane publish_to_s3 version:${BUILDKITE_TAG:-$BUILDKITE_COMMIT}
plugins: *plugins

- label: ':swift: :package: Publish PR XCFramework'
Expand All @@ -114,6 +114,12 @@ steps:
command: .buildkite/publish-pr-xcframework.sh
plugins: *plugins

- label: ':rocket: Publish Swift release $NEW_VERSION'
depends_on: build-xcframework
if: build.env("NEW_VERSION") != null
command: .buildkite/release.sh
plugins: *plugins

- label: ':ios: Test iOS E2E'
depends_on: build-react
command: |
Expand Down
20 changes: 20 additions & 0 deletions .buildkite/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
set -euo pipefail

if [[ -z "${NEW_VERSION:-}" ]]; then
echo "NEW_VERSION is not set; nothing to publish."
exit 0
fi

echo '--- :robot_face: Use bot for Git operations'
source use-bot-for-git

echo '--- :arrow_down: Downloading XCFramework artifacts'
buildkite-agent artifact download '*.xcframework.zip' . --step "build-xcframework"
buildkite-agent artifact download '*.xcframework.zip.checksum.txt' . --step "build-xcframework"

echo '--- :rubygems: Setting up Gems'
install_gems

echo "--- :rocket: Publishing Swift release $NEW_VERSION"
bundle exec fastlane release "version:$NEW_VERSION"
79 changes: 18 additions & 61 deletions bin/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ check_working_directory() {
check_dependencies() {
local missing_deps=()

if ! command -v gh &> /dev/null; then
missing_deps+=("gh (GitHub CLI)")
fi

if ! command -v npm &> /dev/null; then
missing_deps+=("npm")
fi
Expand Down Expand Up @@ -132,20 +128,6 @@ calculate_new_version() {
esac
}

# Function to check if a version is a prerelease
is_prerelease() {
local version=$1

# Use semver to check if the version has prerelease identifiers
local result=$(node -p "require('semver').prerelease('$version') !== null")

if [ "$result" = "true" ]; then
return 0 # It is a prerelease
else
return 1 # It is not a prerelease
fi
}

# Function to validate version type
validate_version_type() {
local version_type=$1
Expand Down Expand Up @@ -262,20 +244,6 @@ commit_changes() {
print_success "Changes committed with message: chore(release): $version"
}

# Function to create git tag
create_tag() {
local version=$1

print_status "Creating git tag: v$version"

if [ "$DRY_RUN" = "true" ]; then
return
fi

git tag "v$version"
print_success "Tag created: v$version"
}

# Function to push changes
push_changes() {
local version=$1
Expand All @@ -286,22 +254,27 @@ push_changes() {
return
fi

git push origin trunk --tags
git push origin trunk
print_success "Changes pushed successfully"
}

# Function to create GitHub release
create_github_release() {
# Function to print the post-push instructions for kicking off the
# Buildkite publish build. CI creates the tag and the GitHub release —
# this script just bumps the version files on trunk.
print_publish_instructions() {
local version=$1
local tag="v$version"

print_status "Creating GitHub release: v$version"

if [ "$DRY_RUN" = "true" ]; then
return
fi

gh release create "v$version" --generate-notes --title "$version"
print_success "GitHub release created: v$version"
echo
print_status "Next: trigger the Buildkite publish build."
echo
echo " 1. Open https://buildkite.com/automattic/gutenbergkit/builds/new"
echo " 2. Branch: trunk"
echo " 3. Environment Variables: NEW_VERSION=$tag"
echo
echo "The :rocket: 'Publish Swift release' step will build + sign the"
echo "XCFramework, upload it to S3, rewrite Package.swift to use the"
echo "binary target, tag $tag, and create the GitHub release."
}

# Main function
Expand Down Expand Up @@ -381,34 +354,18 @@ main() {
commit_changes "$new_version"
echo

create_tag "$new_version"
echo

push_changes "$new_version"
echo

# Only create GitHub release for non-prerelease versions
if is_prerelease "$new_version"; then
print_status "Skipping GitHub release creation for prerelease version"
else
create_github_release "$new_version"
fi
echo

# Summary
print_success "Release process completed successfully!"
print_success "Version bump completed successfully!"
print_status "Version: $current_version -> $new_version"

if [ "$DRY_RUN" = "true" ]; then
print_warning "This was a dry run. No actual changes were made."
print_status "To perform the actual release, run: make release VERSION_TYPE=$version_type"
else
if is_prerelease "$new_version"; then
print_status "Prerelease tag v$new_version has been created and pushed."
print_status "No GitHub release was created for this prerelease version."
else
print_status "The release is ready for integration into the WordPress app."
fi
print_publish_instructions "$new_version"
fi
}

Expand Down
34 changes: 27 additions & 7 deletions docs/releases.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# GutenbergKit Release Process

Use the provided release script to automate the entire process:
Releases happen in two steps: a local script bumps the version on `trunk`, then a CI build creates the tag and the GitHub release.

## Step 1 — Bump versions on trunk

Run the release script:

```bash
# Standard version increments
Expand Down Expand Up @@ -31,13 +35,29 @@ The script:
1. Ensures required dependencies are installed
1. Increments the version number[^1]
1. Builds the project[^2]
1. Commits changes
1. Creates a Git tag
1. Pushes to `origin/trunk` with tags
1. Creates a GitHub release
1. Creates a new release on GitHub: `gh release create vX.X.X --generate-notes --title "X.X.X"`
1. Commits the version bump as `chore(release): X.Y.Z`
1. Pushes to `origin/trunk`

It does **not** create the git tag or the GitHub release — that's Step 2.

## Step 2 — Publish via Buildkite

Trigger a new Buildkite build with `NEW_VERSION` set to the tag name (e.g. `v0.15.3`):

1. Open <https://buildkite.com/automattic/gutenbergkit/builds/new>
2. **Branch**: `trunk`
3. **Environment Variables**: `NEW_VERSION=vX.Y.Z`

The `:rocket: Publish Swift release` step then:

1. Builds and signs the XCFramework
1. Uploads it to `s3://a8c-apps-public-artifacts/gutenbergkit/vX.Y.Z/`
1. Rewrites `Package.swift` to consume the binary target via `.release(version:, checksum:)`
1. Commits the rewrite on a local `release/vX.Y.Z` branch (never pushed)
1. Tags the commit `vX.Y.Z` and pushes only the tag
1. Creates the GitHub Release with auto-generated notes

After the release is created, it is ready for integration into the WordPress app.
The tag's commit lives off `trunk`'s history (parented on `trunk` but only reachable via the tag), so SPM consumers pinning `vX.Y.Z` resolve a `Package.swift` that fetches the prebuilt XCFramework from CDN rather than rebuilding from local sources.

## Release Notes

Expand Down
22 changes: 11 additions & 11 deletions docs/wordpress-app-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ Make sure the path points to your local GutenbergKit clone relative to your Word

1. Copy `local-builds.gradle-example` to `local-builds.gradle`
2. Uncomment the `localGutenbergKitPath` line and set it to your local GutenbergKit path:
```groovy
localGutenbergKitPath = "../GutenbergKit"
```
```groovy
localGutenbergKitPath = "../GutenbergKit"
```
3. Run Gradle sync — this substitutes the Maven dependency with the local project

### Git Revision
Expand Down Expand Up @@ -89,7 +89,7 @@ Available version types:
- `premajor` — increments major and adds alpha suffix (0.13.2 → 1.0.0-alpha.0)
- `prerelease` — increments the alpha number (0.13.3-alpha.0 → 0.13.3-alpha.1)

This pushes a git tag (e.g., `v0.13.3-alpha.0`) and CI publishes the Android build to the Maven repository.
This bumps the version on `trunk`. The git tag and Android Maven publish happen in a follow-up Buildkite build triggered with `NEW_VERSION=v0.13.3-alpha.0`. See [Release Process](./releases.md) for the full flow.

#### iOS

Expand Down Expand Up @@ -127,7 +127,7 @@ Available version types:
- `minor` — new features, backwards compatible (0.13.2 → 0.14.0)
- `major` — breaking changes (0.13.2 → 1.0.0)

This creates a GitHub Release with auto-generated notes and CI publishes the Android build to the Maven repository.
This bumps the version on `trunk`. The git tag, GitHub Release, and Android Maven publish happen in a follow-up Buildkite build triggered with `NEW_VERSION=v0.13.3`. See [Release Process](./releases.md) for the full flow.

#### iOS

Expand All @@ -147,12 +147,12 @@ gutenberg-kit = '0.13.3'

## Workflow Recommendations

| Scenario | Recommended Method |
| --------------------------------- | ------------------ |
| Active feature development | Local Development |
| PR review / testing | Git Revision |
| Merging to WordPress app trunk | Pre-release |
| WordPress app release | Formal Release |
| Scenario | Recommended Method |
| ------------------------------ | ------------------ |
| Active feature development | Local Development |
| PR review / testing | Git Revision |
| Merging to WordPress app trunk | Pre-release |
| WordPress app release | Formal Release |

## Platform-Specific Notes

Expand Down
57 changes: 57 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,63 @@ lane :publish_pr_xcframework do
post_buildkite_annotation(body: body)
end

lane :release do |options|
version = required_version!(options)

validate(version: version)
update_swift_package(version: version)
publish_to_s3(version: version)
publish_release_to_github(version: version)
end

lane :validate do |options|
version = required_version!(options)

UI.user_error!("Tag #{version} already exists on the remote.") \
if git_tag_exists(tag: version, remote: true, remote_name: 'origin')

UI.user_error!("Release #{version} already exists on GitHub.") \
unless get_github_release(url: GITHUB_REPO, version: version).nil?

# `get_github_release` populates these lane-context values; clear them so a
# later action doesn't see stale state from this probe call.
remove_lane_context_values [
SharedValues::GITHUB_API_RESPONSE,
SharedValues::GITHUB_API_STATUS_CODE,
SharedValues::GITHUB_API_JSON
]
end

lane :update_swift_package do |options|
version = required_version!(options)

rewrite_resources_mode!(
File.join(PROJECT_ROOT, 'Package.swift'),
version: version,
checksum: xcframework_checksum
)
end

lane :publish_release_to_github do |options|
version = required_version!(options)

# The new commit is tagged and the tag is pushed to the remote. The
# `release/<version>` branch is local-only and can be discarded — only the
# tag ref is published.
sh("git checkout -b release/#{version}")
git_commit(
path: File.join(PROJECT_ROOT, 'Package.swift'),
message: "Update Package.swift to use version #{version}"
)
add_git_tag(tag: version)
push_git_tags(tag: version, remote: 'origin')

release_args = ['gh', 'release', 'create', version, '--title', version, '--generate-notes']
release_args << '--prerelease' if version.include?('-')
release_args.push(xcframework_file_path, xcframework_checksum_file_path)
sh(*release_args)
end

lane :xcframework_sign do
sh(
'codesign',
Expand Down
Loading