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
25 changes: 25 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Publish to pub.dev

# Publishes to pub.dev when a version tag is pushed. Runs in a TAG ref context —
# the only refType pub.dev's automated publishing authorizes (branch pushes are
# rejected). release.yml creates the tag on merge (via RELEASE_PAT) to trigger
# this.
Comment thread
nixrajput marked this conversation as resolved.
#
# Uses the Dart team's official reusable publish workflow, which performs the
# pub.dev OIDC token exchange and runs `dart pub publish` WITH validation
# (no --force). It is the recommended, maintained path — more robust and secure
# than a hand-rolled publish step.
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"

# Least privilege at the top level; the reusable workflow's job requests the
# id-token it needs. No write scopes are granted here.
permissions: {}

jobs:
publish:
permissions:
id-token: write # OIDC token for keyless pub.dev auth
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
64 changes: 28 additions & 36 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
name: Release

# On push to the default branch (a merged PR): if the pubspec version isn't yet
# tagged, publish to pub.dev (keyless via OIDC), then tag + create a GitHub
# Release. Replaces the old tag-triggered publish.yml — one coherent
# merge -> release -> publish flow, no PAT needed.
# tagged, push the tag v<version> and create a GitHub Release. The tag then
# triggers publish.yml, which publishes to pub.dev FROM A TAG REF (pub.dev's
# automated publishing only allows the 'tag' refType, not 'branch').
#
# pub.dev publishing requires the package's automated-publishing to be enabled
# and this repo + tag pattern authorized (pub.dev admin -> Automated publishing).
# The tag is PUSHED VIA GIT using PUB_RELEASE_TOKEN, because a tag pushed with
# the default GITHUB_TOKEN does NOT trigger other workflows — the PAT-attributed
# push is what makes publish.yml fire. The GitHub Release itself is created with
# the default GITHUB_TOKEN (reliable release perms in-workflow), so the PAT only
# needs `contents: write` for the git push, not access to the releases API.
# If PUB_RELEASE_TOKEN is unset, the tag is pushed with the default token (still
# tagged + released, but publish.yml won't auto-fire — push the tag manually).
on:
push:
branches: ["master"]
Expand All @@ -17,11 +22,10 @@ permissions:

jobs:
release:
name: Publish and release
name: Tag and release
runs-on: ubuntu-latest
permissions:
contents: write # tag + GitHub Release
id-token: write # pub.dev keyless (OIDC) auth
contents: write # create the GitHub Release with GITHUB_TOKEN
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -31,41 +35,29 @@ jobs:
id: v
run: |
version=$(grep -E '^version:' pubspec.yaml | head -1 | awk '{print $2}' | tr -d '\r')
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
- name: Skip if already released
id: guard
- name: Push tag (triggers pub.dev publish)
id: tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# A PAT push is what lets publish.yml trigger; GITHUB_TOKEN falls back.
TOKEN: ${{ secrets.PUB_RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
TAG: ${{ steps.v.outputs.tag }}
run: |
if gh release view "$TAG" >/dev/null 2>&1 \
|| git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
echo "skip=true" >> "$GITHUB_OUTPUT"
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then
echo "Tag $TAG already exists — skipping."
else
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi
- if: steps.guard.outputs.skip == 'false'
uses: dart-lang/setup-dart@v1
with:
sdk: stable
# Use Flutter for Flutter packages; dart-only packages can drop this step.
- if: steps.guard.outputs.skip == 'false'
uses: subosito/flutter-action@v2
with:
channel: stable
- if: steps.guard.outputs.skip == 'false'
name: Install dependencies
run: flutter pub get || dart pub get
- if: steps.guard.outputs.skip == 'false'
name: Publish to pub.dev
run: dart pub publish --force
- if: steps.guard.outputs.skip == 'false'
name: Tag and GitHub Release
git tag "$TAG" "${{ github.sha }}"
# Push over an authenticated URL built from the token; no persisted creds.
git push "https://x-access-token:${TOKEN}@github.com/${{ github.repository }}.git" "$TAG"
echo "created=true" >> "$GITHUB_OUTPUT"
echo "Pushed $TAG ✓ (publish.yml will publish to pub.dev)"
- name: Create GitHub Release
if: steps.tag.outputs.created == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.v.outputs.tag }}
run: |
gh release create "$TAG" --target "${{ github.sha }}" --title "$TAG" --generate-notes
echo "Released + published $TAG ✓"
gh release create "$TAG" --title "$TAG" --generate-notes
echo "Released $TAG ✓"
77 changes: 59 additions & 18 deletions .github/workflows/version-check.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
name: Version Check

# Every PR must bump the pubspec.yaml version. Release tags it on merge; the
# existing publish workflow then publishes to pub.dev on that tag.
# Every PR must set pubspec.yaml to a version STRICTLY GREATER than everything
# actually RELEASED — the latest version on pub.dev and the highest git tag.
# The base-branch pubspec is deliberately NOT a floor: master's version may be
# ahead of what's been published/tagged (e.g. a bump whose publish failed), and
# a version that was never released must remain releasable. This prevents a
# non-incremental publish (which pub.dev rejects) while still allowing the
# current master version to ship if it hasn't been released yet.
on:
pull_request:
branches: ["master"]
Expand All @@ -17,31 +22,67 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # need the base branch to diff the version
fetch-depth: 0 # need base branch + tags to compare
persist-credentials: false
- name: Read versions

- name: Determine highest released version
id: v
run: |
git fetch --no-tags --depth=1 origin "${{ github.base_ref }}"
# Semver core only (drop any +build suffix): pub.dev/tags use x.y.z.
core() { echo "${1%%+*}"; }
read_ver() { grep -E '^version:' "$1" | head -1 | awk '{print $2}' | tr -d '\r'; }
pr=$(read_ver pubspec.yaml)
git show "origin/${{ github.base_ref }}:pubspec.yaml" > /tmp/base_pubspec.yaml
base=$(read_ver /tmp/base_pubspec.yaml)

pr=$(core "$(read_ver pubspec.yaml)")

# 1) latest published version on pub.dev (404 for a new package -> 0.0.0)
name=$(grep -E '^name:' pubspec.yaml | head -1 | awk '{print $2}' | tr -d '\r')
pubdev=$(curl -fsSL "https://pub.dev/api/packages/$name" 2>/dev/null \
| python3 -c "import sys,json;print(json.load(sys.stdin).get('latest',{}).get('version','0.0.0'))" 2>/dev/null || echo "0.0.0")
pubdev=$(core "${pubdev:-0.0.0}")

# 2) highest git tag (the last actually-released tag)
tag=$(git ls-remote --tags origin 2>/dev/null | sed -E 's#.*refs/tags/##' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' | sed 's/^v//' | sort -V | tail -n1)
tag=${tag:-0.0.0}
Comment thread
nixrajput marked this conversation as resolved.

# highest ACTUALLY-RELEASED version (pub.dev + tags only, NOT base pubspec)
highest=$(printf '%s\n%s\n' "$pubdev" "$tag" | sort -V | tail -n1)

echo "pr=$pr" >> "$GITHUB_OUTPUT"
echo "base=$base" >> "$GITHUB_OUTPUT"
- name: Compare
echo "highest=$highest" >> "$GITHUB_OUTPUT"
echo "pubdev=$pubdev tag=$tag -> highest released=$highest ; PR=$pr"

- name: Require PR version to exceed the highest released
env:
PR: ${{ steps.v.outputs.pr }}
HIGHEST: ${{ steps.v.outputs.highest }}
run: |
if [ "$PR" = "$HIGHEST" ]; then
echo "::error::pubspec version $PR already exists (published/tagged). Bump above $HIGHEST."
exit 1
fi
top=$(printf '%s\n%s\n' "$HIGHEST" "$PR" | sort -V | tail -n1)
if [ "$top" != "$PR" ]; then
echo "::error::pubspec version $PR is not greater than the highest released version $HIGHEST."
exit 1
fi
echo "Version $PR is ahead of the highest released ($HIGHEST) ✓"

- name: Require CHANGELOG entry for this version
env:
PR: ${{ steps.v.outputs.pr }}
BASE: ${{ steps.v.outputs.base }}
run: |
echo "base=$BASE pr=$PR"
if [ "$PR" = "$BASE" ]; then
echo "::error::pubspec.yaml version not bumped (still $BASE)."
# pub.dev rejects a publish whose CHANGELOG doesn't mention the version,
# so enforce it here (at the PR gate) instead of failing at publish time.
if [ ! -f CHANGELOG.md ]; then
echo "::error::CHANGELOG.md is missing — add it with an entry for $PR."
exit 1
fi
greater=$(printf '%s\n%s\n' "$BASE" "$PR" | sort -V | tail -n1)
if [ "$greater" != "$PR" ]; then
echo "::error::pubspec.yaml version $PR is lower than base $BASE."
# Match the version as a whole token (e.g. "## [2.4.0]", "## 2.4.0"),
# not as a substring of a longer version.
if grep -qE "(^|[^0-9.])${PR//./\\.}([^0-9.]|$)" CHANGELOG.md; then
echo "CHANGELOG.md mentions $PR ✓"
else
echo "::error::CHANGELOG.md has no entry for $PR. Add a '## $PR' section describing the release."
exit 1
fi
echo "Version bumped $BASE -> $PR ✓"
6 changes: 5 additions & 1 deletion .github/workflows/web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
name: Setup Flutter and Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: check_up_to_date
permissions:
contents: write # flutter-gh-pages pushes the built demo to the gh-pages branch
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand All @@ -47,8 +49,10 @@ jobs:
- name: Get Flutter Dependencies
run: flutter pub get

# Don't block the demo deploy on info/style lints — the CI workflow lints
# separately. Only real errors should stop the Pages publish.
- name: Run Flutter Analyzer (Lint)
run: flutter analyze
run: flutter analyze --no-fatal-infos --no-fatal-warnings

- name: Deploy to GitHub Pages
uses: bluefireteam/flutter-gh-pages@v8
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 3.1.1

- **Chore**: Added automated pub.dev release pipeline (version check, tag, publish) via GitHub Actions.

## 3.1.0

- **Security**: Added `SECURITY.md` for vulnerability reporting and security best practices.
Expand Down
Loading