Repository navigation
ci: automate release tagging, pub.dev publishing and GitHub release #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6e2f6a6
c27d7de
ef8ee46
bee1622
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| name: Publish | ||
|
|
||
| # Publishes tag v<version> 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. | ||
| # 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]+*' ] | ||
|
|
||
| jobs: | ||
| publish: | ||
| runs-on: ubuntu-latest | ||
| environment: pub.dev | ||
| permissions: | ||
| 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 | ||
| with: | ||
| flutter-version: '3.44.0' | ||
| 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 }} | ||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Release notes aren't checked until after the irreversible publish. The CHANGELOG check only lives in Suggestion: extract the notes before the publish step and fail if the file is empty, e.g.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch on hand-pushed tags. Fixed in bee1622: a A note on the heading scenario: on the normal path, Tested locally: the v3.8.0 tag passes (16 note lines), and a tag for a version with no CHANGELOG section fails. |
||
| 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[@]}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| name: Release | ||
|
|
||
| # Merging a version change in pubspec.yaml into master (or a maintenance branch like 3.x) | ||
| # pushes tag v<version>, which triggers publish.yml. | ||
| # On a release PR it only validates, so a missing CHANGELOG section or a version mismatch fails before merge. | ||
| # To retry a failed tag push, re-run that run from the Actions tab. | ||
| on: | ||
| 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@v5 | ||
| 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-running this job can't recover if the GitHub release step fails. Publishing and the GitHub release are steps in one job. If the publish succeeds and
gh release createthen fails (API error, tag problem, etc.), re-running runsflutter pub publish --forceagain. That fails because the version already exists on pub.dev, so the job never gets back to the release step. This contradicts the CLAUDE.md line "If either workflow fails, re-run it from the Actions tab."Suggestion: split into two jobs,
publish, thengithub-releasewithneeds: publish. "Re-run failed jobs" then retries only the release job. Side benefit: onlypublishneedsid-token: writeand thepub.devenvironment, and onlygithub-releaseneedscontents: write. Right now one job holds both permissions while running third-party actions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, the re-run couldn't recover: pub.dev rejects a second upload of the same version. Fixed in bee1622 by splitting into two jobs:
publish:contents: read+id-token: write, runs in thepub.devenvironmentgithub-release:needs: publish,contents: writeonly, no third-party actions"Re-run failed jobs" now retries only the release. The CLAUDE.md line now says to use "Re-run failed jobs", which matches the new behavior.