Skip to content

Commit b9db7e2

Browse files
ci: automate release tagging, pub.dev publishing and GitHub release (#122)
* ci: automate release tagging, pub.dev publishing and GitHub release release.yml validates version files and the CHANGELOG section on release PRs, and pushes tag v<version> when the version change merges. publish.yml publishes the tag to pub.dev via OIDC and creates the GitHub release from the CHANGELOG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: bump actions/checkout to v5 (Node 24) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci: split publish and GitHub release jobs, check tag before publishing - publish and github-release are separate jobs, so a failed release step can be retried without re-running the publish - check the tag is on master or N.x and has a CHANGELOG section before the irreversible publish (covers tags pushed by hand) - drop workflow_dispatch from release.yml; re-running the push run retries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 613c4e2 commit b9db7e2

3 files changed

Lines changed: 120 additions & 5 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Publish
2+
3+
# Publishes tag v<version> to pub.dev, then creates the GitHub release from that version's CHANGELOG section.
4+
# pub.dev authenticates the run with GitHub's OIDC token (no stored secret), and only accepts tag-push runs.
5+
# One-time setup: enable "Publishing from GitHub Actions" on pub.dev (repository optimizely/optimizely-flutter-sdk,
6+
# tag pattern v{{version}}, required environment pub.dev), and add required reviewers to the pub.dev environment here.
7+
# Separate jobs so "Re-run failed jobs" can retry the GitHub release without publishing again.
8+
on:
9+
push:
10+
tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ]
11+
12+
jobs:
13+
publish:
14+
runs-on: ubuntu-latest
15+
environment: pub.dev
16+
permissions:
17+
contents: read
18+
id-token: write
19+
steps:
20+
- uses: actions/checkout@v5
21+
with:
22+
fetch-depth: 0
23+
persist-credentials: false
24+
# Tags can also be pushed by hand, bypassing release.yml, so check before the irreversible publish
25+
- name: Check tag
26+
run: |
27+
VERSION="${GITHUB_REF_NAME#v}"
28+
git branch -r --contains "$GITHUB_SHA" | grep -qE '^ *origin/(master|[0-9]+\.x)$' \
29+
|| { echo "$GITHUB_REF_NAME is not on master or a maintenance branch"; exit 1; }
30+
awk -v h="## $VERSION" '$0==h{f=1;next} /^## [0-9]/{if(f)exit} f' CHANGELOG.md > notes.md
31+
[ -s notes.md ] || { echo "CHANGELOG.md has no '## $VERSION' section"; exit 1; }
32+
# Exchanges the OIDC token for a pub.dev credential used by `flutter pub publish`
33+
- uses: dart-lang/setup-dart@v1
34+
- uses: subosito/flutter-action@v2
35+
with:
36+
flutter-version: '3.44.0'
37+
channel: 'stable'
38+
- name: Publish to pub.dev
39+
run: flutter pub publish --force
40+
41+
github-release:
42+
needs: publish
43+
runs-on: ubuntu-latest
44+
permissions:
45+
contents: write
46+
steps:
47+
- uses: actions/checkout@v5
48+
with:
49+
fetch-depth: 0
50+
persist-credentials: false
51+
- name: Create GitHub release
52+
env:
53+
GH_TOKEN: ${{ github.token }}
54+
run: |
55+
VERSION="${GITHUB_REF_NAME#v}"
56+
awk -v h="## $VERSION" '$0==h{f=1;next} /^## [0-9]/{if(f)exit} f' CHANGELOG.md > notes.md
57+
FLAGS=()
58+
[[ "$VERSION" == *-* ]] && FLAGS+=(--prerelease)
59+
git merge-base --is-ancestor "$GITHUB_SHA" origin/master || FLAGS+=(--latest=false)
60+
gh release create "$GITHUB_REF_NAME" --verify-tag --title "Release $VERSION" \
61+
--notes-file notes.md "${FLAGS[@]}"

‎.github/workflows/release.yml‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Release
2+
3+
# Merging a version change in pubspec.yaml into master (or a maintenance branch like 3.x)
4+
# pushes tag v<version>, which triggers publish.yml.
5+
# On a release PR it only validates, so a missing CHANGELOG section or a version mismatch fails before merge.
6+
# To retry a failed tag push, re-run that run from the Actions tab.
7+
on:
8+
push:
9+
branches: [ master, '[0-9]+.x' ]
10+
paths: [ pubspec.yaml ]
11+
pull_request:
12+
branches: [ master, '[0-9]+.x' ]
13+
paths: [ pubspec.yaml ]
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
tag:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v5
23+
with:
24+
persist-credentials: false
25+
- name: Validate version
26+
id: version
27+
run: |
28+
VERSION=$(sed -nE 's/^version: *([^ ]+).*/\1/p' pubspec.yaml)
29+
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]] || { echo "Invalid version: $VERSION"; exit 1; }
30+
grep -qF "version = '$VERSION';" lib/package_info.dart || { echo "lib/package_info.dart does not match $VERSION"; exit 1; }
31+
grep -qF "optimizely_flutter_sdk: ^$VERSION" README.md || { echo "README.md does not match $VERSION"; exit 1; }
32+
if git ls-remote --exit-code --tags origin "refs/tags/v$VERSION" >/dev/null; then
33+
echo "v$VERSION already released"; exit 0
34+
fi
35+
grep -qxF "## $VERSION" CHANGELOG.md || { echo "CHANGELOG.md has no '## $VERSION' section"; exit 1; }
36+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
37+
- uses: subosito/flutter-action@v2
38+
if: steps.version.outputs.version
39+
with:
40+
flutter-version: '3.44.0'
41+
channel: 'stable'
42+
- name: Validate package
43+
if: steps.version.outputs.version
44+
run: flutter pub publish --dry-run
45+
- name: Push tag
46+
if: steps.version.outputs.version && github.event_name != 'pull_request'
47+
env:
48+
# A tag pushed with GITHUB_TOKEN would not trigger publish.yml
49+
GH_TOKEN: ${{ secrets.CI_USER_TOKEN }}
50+
VERSION: ${{ steps.version.outputs.version }}
51+
run: gh api "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/v$VERSION" -f sha="$GITHUB_SHA"

‎CLAUDE.md‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ Claude will execute the full workflow:
9999
- ✅ Commit and push
100100
- ✅ Create PR
101101

102+
### After the release PR merges (automated)
103+
104+
- `release.yml` validates the version (pubspec.yaml, package_info.dart, README.md, CHANGELOG section) on the PR, and on merge pushes tag `vX.Y.Z`
105+
- `publish.yml` runs on the tag: publishes to pub.dev (waits for approval in the `pub.dev` environment), then creates the GitHub release from the CHANGELOG section
106+
- Versions with a `-` suffix (e.g. `4.0.0-rc.1`) are released as prereleases
107+
- If a workflow fails, use "Re-run failed jobs" in the Actions tab. A failed GitHub release step is retried without publishing again
108+
102109
### Manual Release
103110

104111
```bash
@@ -117,11 +124,7 @@ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
117124
git push -u origin prepare-X.Y.Z
118125
gh pr create --title "[FSSDK-XXXXX] prepare for release X.Y.Z"
119126

120-
# 6. After merge, create GitHub release
121-
gh release create vX.Y.Z --title "Release X.Y.Z" --draft --target master
122-
123-
# 7. Publish to pub.dev
124-
flutter pub publish
127+
# 6. After merge, the tag, pub.dev publish and GitHub release are automated (see above)
125128
```
126129

127130
### CHANGELOG Format

0 commit comments

Comments
 (0)