Skip to content

ci: upstream compatibility alerts and auto-publish to pub.dev - #30

Merged
MAUstaoglu merged 3 commits into
mainfrom
ci/upstream-compat-and-autopublish
Nov 8, 2026
Merged

MAUstaoglu merged 3 commits into
mainfrom
ci/upstream-compat-and-autopublish

Conversation

@MAUstaoglu

@MAUstaoglu MAUstaoglu commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Two automations for this repo: daily alerts when a newer upstream release or a new Flutter stable breaks or excludes one of our plugins, and automatic publishing to pub.dev when a version bump lands on main.

Upstream compatibility (upstream-compat.yml)

Runs daily at 05:17 UTC. It can also be started by hand, with optional inputs to check a single package, pick a Flutter version, or do a dry run. It always uses the newest Flutter stable, unlike CI, which is pinned.

For each package, .github/scripts/upstream_check.py runs two passes on a scratch copy of packages/:

  • current: flutter pub upgrade, the newest releases within our constraints.
  • major: only when pub outdated shows a newer major outside our constraint. It runs pub upgrade --major-versions on our direct dependencies. That command quietly stays on the old major when something else blocks the new one, so for anything left behind the pass also runs pub add <dep>:^<latest>. That way the pass either tests the new major for real or records pub's explanation of what is blocking it.

Each pass then runs the same checks as the CI package job: analyze lib and test, flutter test, and resolve and analyze the example. Every package gets one of these results:

result meaning
broken fails within our current constraints; users are affected now
major-breaks a new upstream major exists outside our constraint, and moving to it fails
lag a new upstream major exists outside our constraint, and moving to it passes; widening the constraint is the fix
ok nothing to do

.github/scripts/upstream_issues.py keeps one issue per package (Upstream compatibility: <package>, label upstream-compat). The issue body carries a hidden fingerprint of the finding:

  • An open issue is edited only when the fingerprint changes. An unchanged finding produces no edit and no comment.
  • The issue closes itself once the package passes again.
  • A finding identical to one a maintainer closed by hand stays closed. A different finding opens a new issue.
  • An issue the workflow closed itself is marked auto-closed, so the same finding coming back later opens a new issue rather than staying silent.
  • Upstream versions count by caret line: an upstream patch or minor does not touch open issues; a new major does.
  • A dependency counts as held back only when our declared constraint excludes its newest release. SDK-pinned packages such as meta, and dependencies another package caps, don't produce findings.
  • If the check script itself fails (for example, pub.dev is unreachable: pub's exit code 69 or its network errors in a resolve step), that package's job goes red and no issue is filed.

Expect a batch of firebase issues on the first run

A local run on Flutter 3.47.9 found that firebase_auth_tvos can't move to firebase_auth 7.0.1. That version needs firebase_core ^5.1.0, but firebase_core_tvos pins ^4.13.0. Every package that depends on firebase_core_tvos will probably report the same major-breaks on the first run, so expect several issues at once. They all point to the same fix: port firebase_core_tvos to firebase_core 5. In the same run, shared_preferences_tvos and url_launcher_tvos came back ok.

Auto-publish (release.yml → publish.yml)

release.yml runs after CI succeeds on a push to main, or when started by hand to retry. .github/scripts/release_plan.py picks every package whose pubspec version isn't on pub.dev yet. Such a version must also:

  • match the newest ## heading in CHANGELOG.md, and
  • be newer than every version already on pub.dev.

If either check fails, that package isn't released and the run ends red. Healthy packages in the same merge still go out.

Each release candidate goes through two jobs:

  • verify re-runs pub get, analyze, tests and flutter pub publish --dry-run on that exact commit. Because this runs third-party code, it gets a read-only token, no credentials persisted in .git/config, and no shared Flutter/pub cache.
  • tag holds the write permissions (contents, actions) and runs no package code (no checkout). It creates the tag <package>-v<version> and starts publish.yml on that tag, but only for packages whose verify passed. One failing package doesn't hold back the others.

Why the release workflow starts the publish workflow itself: a tag created with GITHUB_TOKEN doesn't trigger on: push workflows, but GITHUB_TOKEN is allowed to start a workflow_dispatch run. pub.dev accepts publishing from a workflow_dispatch run on a tag ref when that event is enabled for the package. So no PAT or GitHub App is needed. Pushing a tag by hand also triggers publish.yml directly.

Started by hand, Release runs only on main. No Publish is dispatched while one for the same tag is still queued, running or awaiting approval. A tag that already exists is re-dispatched only when it points at the commit just verified; a tag on an older commit fails the run with "bump the version or delete the tag".

publish.yml checks that the tagged commit is an ancestor of main (git merge-base --is-ancestor) and that the tag matches the pubspec version and the changelog. If pub.dev already has that version, it stops there, green. Otherwise it calls dart-lang's reusable publish.yml, pinned to commit 6afc89d (v1), which publishes over OIDC in the pub.dev environment. That workflow only accepts environment and working-directory, so it installs the newest Flutter stable rather than CI's pinned version. Release's dry-run uses the pinned one.

Today all 25 packages match their pub.dev versions, so merging this releases nothing.

pub.dev setup (manual, per package)

On pub.dev/packages/<pkg>/admin → Automated publishing → GitHub Actions:

  • Enable publishing from GitHub Actions, with repository fluttertv/plugins
  • Set the tag pattern to <pkg>-v{{version}}
  • Enable both the push and workflow_dispatch events. Without workflow_dispatch, automatic releases fail at upload.
  • Require the GitHub Actions environment pub.dev (recommended)

On GitHub:

  • Settings → Environments → create pub.dev, and under Deployment branches and tags choose Selected branches and tags with a tag rule *_tvos-v*. That way only release tags can deploy to it. Required reviewers are optional.
  • Make sure no tag ruleset stops github-actions from creating *_tvos-v* tags.

A brand-new package still needs its first version published by hand (flutter pub publish), because pub.dev only allows automated publishing for packages that already exist.

Also in this PR

  • AUTHORING.md: a new "7. Releasing" section
  • .gitignore: __pycache__/

Tested

  • release_plan.py: covered a version bump, a missing changelog entry, a version older than pub.dev, and an already-existing tag. On main today it finds nothing to release.
  • Issue deduplication: all six open/closed/same/changed cases, using a stubbed gh. Also a --dry-run against this repo's real issues.
  • check_repo.py --selftest still passes.
  • After the independent review: scenario tests with stubbed gh/flutter for auto-close vs hand-close, reopened issues, patch vs major fingerprints, the network-error exit, build-metadata ordering and annotated-tag SHAs.
  • Second review round: release-plan cases incl. tag SHAs, pub build-metadata ordering, constraint-based lag detection (meta-style SDK pins, ranges, carets below 1.0), exit 69, bot-closed vs hand-closed issues, plus the release Tag state step (tag on another commit, active Publish run) and the pub.dev already-published step run as-is with a stubbed gh and the real pub.dev API.
  • Not yet tested: none of these workflows has run on GitHub yet. After merge, a manual dry run of Upstream compatibility is the first thing to try.

Upstream compatibility (daily + manual): for each package, test the newest
releases within our constraints, and the newest upstream majors when pub
outdated reports any, with the same checks as the CI package job, on the
newest Flutter stable. When pub upgrade --major-versions cannot reach a new
major, `pub add name:^latest` forces it, so the leg tests the new major or
reports what blocks it instead of passing on the old one. One issue per
package under the upstream-compat label, touched only when its fingerprint
changes and closed once the package passes again.

Release: after CI succeeds on a push to main, packages whose pubspec version
is not on pub.dev (and tops CHANGELOG.md, and is newer than pub.dev) are
re-verified with analyze, tests and publish --dry-run, tagged
<package>-v<version>, and handed to Publish via workflow_dispatch, because a
GITHUB_TOKEN tag push does not trigger workflows.

Publish: dart-lang/setup-dart's reusable publish workflow (OIDC) in the
pub.dev environment, after checking the tag against pubspec and changelog.
Upstream compatibility:
- An issue the workflow closed itself (package passing again) is marked
  auto-closed and never silences a later identical finding; only a
  hand-closed issue does. A hand-reopened auto-closed issue loses the marker.
- Fingerprints and step names count upstream releases by caret line, so an
  upstream patch or minor no longer edits open issues or reopens hand-closed
  ones; a new major still does.
- A resolve step that fails with pub's network errors makes the check exit 4
  with no report instead of filing a `broken` issue.
- outdated rows need a newer caret line, not just any newer release.

Release and publish:
- Release dispatched by hand only runs on main.
- An existing tag is re-dispatched only when it points at the verified commit
  and no Publish run for it is still active; a tag on an older commit fails
  the run.
- Publish refuses a tag whose commit is not on main.
- release_plan orders build metadata as pub does and reports tag SHAs
  (peeled for annotated tags).
- Corrected the description of dart-lang's reusable publish workflow.
…g test

Release:
- Split the release job. `verify` runs pub get, analyze, tests and the
  publish dry-run with a read-only token, no persisted credentials and no
  shared Flutter/pub cache; `tag` holds contents/actions write and runs no
  package code (no checkout). A per-package artifact marker carries the
  verified commit across, so one package failing does not hold back the
  others in the same merge.
- The active-Publish check now runs before every dispatch, not only for
  existing tags.
- matrix and step values reach run: blocks through env, not ${{ }}.

Publish:
- The on-main check uses `git merge-base --is-ancestor` against a full
  fetch instead of the compare API.
- `resolve` skips publishing, green, when pub.dev already has the version,
  so a duplicate dispatch or a re-run cannot fail at upload.
- dart-lang's reusable publish workflow is pinned to 6afc89d (v1). Its
  only inputs are environment and working-directory, so Flutter cannot be
  pinned to CI's version there.

Upstream compatibility:
- A dependency counts as held back only when our declared constraint
  excludes its newest release. SDK-pinned packages such as meta, and
  dependencies another package caps, no longer produce lag findings.
- pub's exit code 69 (package server unavailable) is a tooling failure,
  alongside the existing network-error wording.
@MAUstaoglu
MAUstaoglu merged commit 0b64266 into main Nov 8, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant