From 6e2f6a6c27dbf405e176806e1ee91ab30cd737a0 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Wed, 14 Oct 2026 20:54:56 +0600 Subject: [PATCH 1/3] 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 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 --- .github/workflows/publish.yml | 41 +++++++++++++++++++++++++++ .github/workflows/release.yml | 52 +++++++++++++++++++++++++++++++++++ CLAUDE.md | 13 +++++---- 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..34f5130 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,41 @@ +name: Publish + +# Publishes tag v to pub.dev, then creates the GitHub release from that version's CHANGELOG section. +# pub.dev authenticates the run with GitHub's OIDC token (no stored secret), and only accepts tag-push runs. +# One-time setup: enable "Publishing from GitHub Actions" on pub.dev (repository optimizely/optimizely-flutter-sdk, +# tag pattern v{{version}}, required environment pub.dev), and add required reviewers to the pub.dev environment here. +on: + push: + tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ] + +jobs: + publish: + runs-on: ubuntu-latest + environment: pub.dev + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + # Exchanges the OIDC token for a pub.dev credential used by `flutter pub publish` + - uses: dart-lang/setup-dart@v1 + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.44.0' + channel: 'stable' + - name: Publish to pub.dev + run: flutter pub publish --force + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="${GITHUB_REF_NAME#v}" + awk -v h="## $VERSION" '$0==h{f=1;next} /^## [0-9]/{if(f)exit} f' CHANGELOG.md > notes.md + FLAGS=() + [[ "$VERSION" == *-* ]] && FLAGS+=(--prerelease) + git merge-base --is-ancestor "$GITHUB_SHA" origin/master || FLAGS+=(--latest=false) + gh release create "$GITHUB_REF_NAME" --verify-tag --title "Release $VERSION" \ + --notes-file notes.md "${FLAGS[@]}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..998c405 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +# Merging a version change in pubspec.yaml into master (or a maintenance branch like 3.x) +# pushes tag v, which triggers publish.yml. +# On a release PR it only validates, so a missing CHANGELOG section or a version mismatch fails before merge. +# Run manually (workflow_dispatch) to retry a failed release from the branch's latest commit. +on: + workflow_dispatch: + push: + branches: [ master, '[0-9]+.x' ] + paths: [ pubspec.yaml ] + pull_request: + branches: [ master, '[0-9]+.x' ] + paths: [ pubspec.yaml ] + +permissions: + contents: read + +jobs: + tag: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Validate version + id: version + run: | + VERSION=$(sed -nE 's/^version: *([^ ]+).*/\1/p' pubspec.yaml) + [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]] || { echo "Invalid version: $VERSION"; exit 1; } + grep -qF "version = '$VERSION';" lib/package_info.dart || { echo "lib/package_info.dart does not match $VERSION"; exit 1; } + grep -qF "optimizely_flutter_sdk: ^$VERSION" README.md || { echo "README.md does not match $VERSION"; exit 1; } + if git ls-remote --exit-code --tags origin "refs/tags/v$VERSION" >/dev/null; then + echo "v$VERSION already released"; exit 0 + fi + grep -qxF "## $VERSION" CHANGELOG.md || { echo "CHANGELOG.md has no '## $VERSION' section"; exit 1; } + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + - uses: subosito/flutter-action@v2 + if: steps.version.outputs.version + with: + flutter-version: '3.44.0' + channel: 'stable' + - name: Validate package + if: steps.version.outputs.version + run: flutter pub publish --dry-run + - name: Push tag + if: steps.version.outputs.version && github.event_name != 'pull_request' + env: + # A tag pushed with GITHUB_TOKEN would not trigger publish.yml + GH_TOKEN: ${{ secrets.CI_USER_TOKEN }} + VERSION: ${{ steps.version.outputs.version }} + run: gh api "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/v$VERSION" -f sha="$GITHUB_SHA" diff --git a/CLAUDE.md b/CLAUDE.md index 4e2b952..523e585 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -102,6 +102,13 @@ Claude will execute the full workflow: - ✅ Commit and push - ✅ Create PR +### After the release PR merges (automated) + +- `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` +- `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 +- Versions with a `-` suffix (e.g. `4.0.0-rc.1`) are released as prereleases +- If either workflow fails, re-run it from the Actions tab + ### Manual Release ```bash @@ -120,11 +127,7 @@ Co-Authored-By: Claude Sonnet 4.5 " git push -u origin prepare-X.Y.Z gh pr create --title "[FSSDK-XXXXX] prepare for release X.Y.Z" -# 6. After merge, create GitHub release -gh release create vX.Y.Z --title "Release X.Y.Z" --draft --target master - -# 7. Publish to pub.dev -flutter pub publish +# 6. After merge, the tag, pub.dev publish and GitHub release are automated (see above) ``` ### CHANGELOG Format From ef8ee466cb7ae82665bbeb3bfb55dd930ea86f1e Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 15 Oct 2026 22:19:39 +0600 Subject: [PATCH 2/3] ci: bump actions/checkout to v5 (Node 24) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 34f5130..9a759ba 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 998c405..ab78447 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: tag: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Validate version From bee1622655948234bb959594736c4ca4b7ae15f6 Mon Sep 17 00:00:00 2001 From: Muzahidul Islam Date: Thu, 15 Oct 2026 22:47:22 +0600 Subject: [PATCH 3/3] 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 --- .github/workflows/publish.yml | 22 +++++++++++++++++++++- .github/workflows/release.yml | 3 +-- CLAUDE.md | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9a759ba..b707c1f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ name: Publish # pub.dev authenticates the run with GitHub's OIDC token (no stored secret), and only accepts tag-push runs. # One-time setup: enable "Publishing from GitHub Actions" on pub.dev (repository optimizely/optimizely-flutter-sdk, # tag pattern v{{version}}, required environment pub.dev), and add required reviewers to the pub.dev environment here. +# Separate jobs so "Re-run failed jobs" can retry the GitHub release without publishing again. on: push: tags: [ 'v[0-9]+.[0-9]+.[0-9]+*' ] @@ -13,13 +14,21 @@ jobs: runs-on: ubuntu-latest environment: pub.dev permissions: - contents: write + contents: read id-token: write steps: - uses: actions/checkout@v5 with: fetch-depth: 0 persist-credentials: false + # Tags can also be pushed by hand, bypassing release.yml, so check before the irreversible publish + - name: Check tag + run: | + VERSION="${GITHUB_REF_NAME#v}" + git branch -r --contains "$GITHUB_SHA" | grep -qE '^ *origin/(master|[0-9]+\.x)$' \ + || { echo "$GITHUB_REF_NAME is not on master or a maintenance branch"; exit 1; } + awk -v h="## $VERSION" '$0==h{f=1;next} /^## [0-9]/{if(f)exit} f' CHANGELOG.md > notes.md + [ -s notes.md ] || { echo "CHANGELOG.md has no '## $VERSION' section"; exit 1; } # Exchanges the OIDC token for a pub.dev credential used by `flutter pub publish` - uses: dart-lang/setup-dart@v1 - uses: subosito/flutter-action@v2 @@ -28,6 +37,17 @@ jobs: channel: 'stable' - name: Publish to pub.dev run: flutter pub publish --force + + github-release: + needs: publish + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false - name: Create GitHub release env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab78447..5f18100 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,9 +3,8 @@ name: Release # Merging a version change in pubspec.yaml into master (or a maintenance branch like 3.x) # pushes tag v, which triggers publish.yml. # On a release PR it only validates, so a missing CHANGELOG section or a version mismatch fails before merge. -# Run manually (workflow_dispatch) to retry a failed release from the branch's latest commit. +# To retry a failed tag push, re-run that run from the Actions tab. on: - workflow_dispatch: push: branches: [ master, '[0-9]+.x' ] paths: [ pubspec.yaml ] diff --git a/CLAUDE.md b/CLAUDE.md index e8ce890..cf95d75 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -104,7 +104,7 @@ Claude will execute the full workflow: - `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` - `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 - Versions with a `-` suffix (e.g. `4.0.0-rc.1`) are released as prereleases -- If either workflow fails, re-run it from the Actions tab +- If a workflow fails, use "Re-run failed jobs" in the Actions tab. A failed GitHub release step is retried without publishing again ### Manual Release