Skip to content

ci(repo): add automated per-package pub.dev publishing - #142

Open
xsahil03x wants to merge 39 commits into
mainfrom
sahil/flu-636-automated-package-publishing-for-core
Open

ci(repo): add automated per-package pub.dev publishing#142
xsahil03x wants to merge 39 commits into
mainfrom
sahil/flu-636-automated-package-publishing-for-core

Conversation

@xsahil03x

@xsahil03x xsahil03x commented Jul 30, 2026

Copy link
Copy Markdown
Member

What

Automated pub.dev publishing for this repo, matching its independent per-package versioning (each package releases on its own <pkg>-vX.Y.Z tag). Ports the proven design used by stream-chat-flutter / flame / supabase-flutter / bluefireteam/melos-action, wired through the repo's bot PAT.

Closes FLU-636. Manual setup (bot token + pub.dev automated-publishing config) was completed in FLU-638.

How it works

release_tag.yml — on a chore(...): release merge to main:

  • Tags every package whose current version is not yet on pub.dev (melos exec --no-published), so tags are derived from package state, not the commit message — robust to multi-package release PRs and title typos.
  • Pushes tags dependency-first (--order-dependents) with the bot PAT (a GITHUB_TOKEN tag push would not trigger the publish workflow).

release_publish.yml — on a <pkg>-vX.Y.Z tag push (+ workflow_dispatch for re-runs):

  • Parses the tag, verifies it against packages/<pkg>/pubspec.yaml.
  • Dry-run → dependency wait (polls pub.dev's per-version endpoint until each in-workspace dependency is live before publishing) → scoped publish via OIDC (id-token: write, no stored credentials).
  • Creates a per-package GitHub Release from that package's ## X.Y.Z CHANGELOG section.

release-pr skill (.claude/skills/release-pr/) — opens the release PR: detects each package's ## Upcoming changelog, bumps versions, promotes the changelog heading, and opens a single PR from a release/… branch. Never versions/tags/publishes/merges itself — that's the workflows' job.

STYLE_GUIDE.md — documents the release flow (release from a release/… branch, single PR may bump many packages, dependent order is automatic).

Notable design decisions

  • Per-package tags + one publish run per tag — required: pub.dev binds one OIDC ref → one package's tag pattern, so N packages need N runs. (Lockstep repos like chat can publish all in one run; we can't.)
  • Dependency wait instead of best-effort ordering — GitHub runs the per-package publish workflows concurrently, and --order-dependents on the trigger only orders firing, not completion. flame/supabase live with best-effort + manual re-run; the wait makes ours self-healing (no re-run for the stream_corestream_core_flutter case). It polls the per-version pub.dev endpoint, which stays fresh in CI — the package listing melos --published reads lags for minutes.
  • Not --skip-validation — would let a dependent publish before its dependency is live, leaving it unresolvable if the dependency's run fails. The wait keeps a dependent from ever publishing against a missing dependency.
  • make_latest: false — with independent versioning no single release is the repo's "latest"; a stream_thumbnail patch must not outrank a stream_core_flutter release for the badge.
  • cancel-in-progress: false on both — never cancel a run mid tag-push / mid-publish.
  • Not generate_release_notes — it would list every repo commit since the previous tag of any package; we extract the package's own CHANGELOG section (matched by version token, so a dated ## X.Y.Z (date) heading still resolves).

Verification

Validated end-to-end with real pub.dev publishes on a dedicated test monorepo (automated publishing enabled), covering the full matrix:

  • Single package release, and multiple independent packages in one PR.
  • Dependency wait — 3-deep chain, fan-out, and diamond graphs: each dependent detected its dependency's just-published version and published in dependency order.
  • Gate timeout + recovery — a dependent whose dependency never lands times out with a clear re-run message; re-running the dependency then the dependent succeeds (publishing is idempotent).
  • Pre-release tags (-beta.xprerelease: true), tag-parse guard (malformed tag fails fast), non-release commit correctly skips release_tag, and idempotent re-runs of an already-published tag are a no-op.
  • release-pr skill authored the release PR; the merged PR then drove the workflows above.

Repo-side checks: actionlint clean on both workflows; melos run lint:pub (publish dry-run) passes for all three packages.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added automated workflows for tagging, validating, and publishing package releases.
    • Releases are published in dependency order, skip existing versions, and include changelog links.
    • Added a guided process for preparing release pull requests.
  • Documentation

    • Documented release procedures, versioning, changelog requirements, dependency ordering, retries, and timeout handling.
  • Chores

    • Added linting and dry-run checks before publication.
    • Added secure, automated publishing with identity-based authentication.

xsahil03x and others added 10 commits July 29, 2026 17:25
Replace StreamReactions.onPressed (a bare VoidCallback shared by every chip) with onReactionPressed, which reports the pressed StreamReactionsItem — or null for the cluster/overflow chip, which represents no single reaction. Add an optional StreamReactionsItem.key so callers can identify the pressed item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ction-pressed

# Conflicts:
#	packages/stream_core_flutter/CHANGELOG.md
#	packages/stream_core_flutter/test/components/reaction/stream_reactions_test.dart
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nPressed in gallery

Extract the callback type into OnReactionItemPressed (mirrors OnReactionItemPicked). Migrate the design system gallery to onReactionPressed and demonstrate it by showing a snackbar with the tapped emoji.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ssed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng it

Keep onPressed (VoidCallback) as a deprecated fallback alongside the new onReactionPressed, so the change is non-breaking (mirrors the chat onReactionsTap deprecation). onReactionPressed wins when both are set; onPressed is folded in via the chip callback. Requires a few deprecated_member_use ignores since core runs --fatal-infos + deprecated_consistency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deprecate only the public StreamReactions constructor onPressed params (which is what warns callers) and leave the internal StreamReactionsProps field un-annotated, mirroring the chat StreamMessageItem pattern. Referencing the deprecated ctor param in its own initializer isn't flagged, and internal props access is clean — so no // ignore comments are needed in lib.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ction-pressed

# Conflicts:
#	packages/stream_core_flutter/CHANGELOG.md
Add tag + publish workflows for the repo's independent per-package
versioning, so merging a release PR publishes each bumped package to
pub.dev via OIDC (no stored credentials) and cuts a GitHub Release.

- release_tag.yml: on a `chore(...): release` merge to main, tag every
  package whose current version is not yet on pub.dev
  (`<pkg>-vX.Y.Z`, derived from package state — robust to multi-package
  release PRs and title typos) and push the tags dependency-first.
- release_publish.yml: on a `<pkg>-vX.Y.Z` tag push (+ workflow_dispatch
  for re-runs), verify the tag against the pubspec, publish that one
  package (OIDC), and create a per-package GitHub Release from its
  CHANGELOG section. A dependency gate waits until each in-workspace
  dependency is live on pub.dev before publishing, guaranteeing
  dependent order without a manual re-run. Releases are never marked
  "latest" since packages version independently.
- STYLE_GUIDE.md: document the release flow.

Manual setup (bot token + pub.dev config) done in FLU-638.
Closes FLU-636.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x requested a review from a team as a code owner July 30, 2026 14:57
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds release preparation guidance and a release-pr skill. Adds workflows for dependency-ordered package tagging, validation, pub.dev publishing, changelog extraction, and GitHub Release creation.

Changes

Release automation

Layer / File(s) Summary
Release preparation process
.claude/skills/release-pr/SKILL.md, STYLE_GUIDE.md
Defines release branches, package versions, changelogs, validation, commits, pull requests, tagging, publishing, dependency handling, timeouts, and retries.
Dependency-ordered release tagging
.github/workflows/release_tag.yml, melos.yaml
Runs tagging after qualifying release commits and pushes unpublished package-version tags in dependency order.
Validated package publishing and GitHub Releases
.github/workflows/release_publish.yml, melos.yaml
Validates package tags and versions, waits for dependency versions on pub.dev, publishes the scoped package, extracts changelog notes, and creates a prerelease-aware GitHub Release.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PackageTag
  participant release_publish
  participant PubDev
  participant Melos
  participant GitHubRelease
  PackageTag->>release_publish: package and version tag
  release_publish->>Melos: bootstrap and lint package
  release_publish->>PubDev: poll dependency versions
  PubDev-->>release_publish: required versions available
  release_publish->>Melos: publish scoped package
  release_publish->>PubDev: verify package publication
  release_publish->>GitHubRelease: create release with changelog notes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: automated per-package pub.dev publishing.
Description check ✅ Passed The description explains the implementation, design decisions, testing, ticket, and release flow, but omits the template headings and CLA checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sahil/flu-636-automated-package-publishing-for-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@xsahil03x
xsahil03x marked this pull request as draft July 30, 2026 14:58
@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.70%. Comparing base (ab4b1fb) to head (998e449).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #142   +/-   ##
=======================================
  Coverage   54.70%   54.70%           
=======================================
  Files         185      185           
  Lines        7575     7575           
=======================================
  Hits         4144     4144           
  Misses       3431     3431           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
.github/workflows/release_publish.yml (1)

172-184: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider gh release create instead of a third-party action.

The GitHub-hosted runner already ships gh, so gh release create --title ... --notes-file ... ${{ github.ref_name }} would drop the third-party action dependency. Purely optional — action-gh-release is a widely-trusted, well-maintained action, so the practical benefit here is marginal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release_publish.yml around lines 172 - 184, Optionally
replace the softprops/action-gh-release step with a native gh release create
command, passing the parsed package/version as the title, the notes output path
as the notes file, and github.ref_name as the tag. Preserve prerelease handling,
make_latest: false behavior, and authentication through the existing token.

Source: Linters/SAST tools

.github/workflows/release_tag.yml (1)

23-29: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Reduce bot-PAT credential exposure window in both release workflows.

Both workflows check out with the bot PAT and the default persist-credentials: true, so the write-scoped token sits in git config through every later step — including flutter pub global activate melos / melos bootstrap, which execute third-party pub.dev code. The fix differs per file because only one of them actually needs the persisted credential:

  • .github/workflows/release_tag.yml#L23-L29: still needs to git push tags later, so keep the credential but scope it — e.g. persist-credentials: false plus an explicit git remote set-url origin https://x-access-token:${TOKEN}@github.com/... immediately before the push step, so the token isn't sitting in git config during the Flutter/melos install steps.
  • .github/workflows/release_publish.yml#L23-L27: never pushes to git (the GitHub Release step already passes token: explicitly at line 184), so persist-credentials: false can be set here with no functional change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release_tag.yml around lines 23 - 29, Reduce bot PAT
exposure in both checkout steps: in .github/workflows/release_tag.yml at lines
23-29, disable persisted credentials and configure the authenticated origin
immediately before the later tag push using the bot token; in
.github/workflows/release_publish.yml at lines 23-27, disable persisted
credentials since no git push occurs and the release action supplies its token
explicitly.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release_publish.yml:
- Around line 39-45: Harden the tag parsing around the release workflow’s parse
step by replacing the unbounded version capture in the tag regex with the
repository’s expected semver-shaped pattern. For every affected run step, expose
the parsed package and version through step-level env variables and reference
those shell variables instead of directly interpolating
steps.parse.outputs.package/version in the script; preserve the existing release
behavior.

In
`@packages/stream_core_flutter/lib/src/components/reaction/stream_reactions.dart`:
- Around line 197-202: Add `@Deprecated` annotations to the legacy onPressed
constructor parameter and corresponding final field in StreamReactionsProps,
preserving the existing deprecation guidance used by the widget-level API and
leaving onReactionPressed unchanged.

---

Nitpick comments:
In @.github/workflows/release_publish.yml:
- Around line 172-184: Optionally replace the softprops/action-gh-release step
with a native gh release create command, passing the parsed package/version as
the title, the notes output path as the notes file, and github.ref_name as the
tag. Preserve prerelease handling, make_latest: false behavior, and
authentication through the existing token.

In @.github/workflows/release_tag.yml:
- Around line 23-29: Reduce bot PAT exposure in both checkout steps: in
.github/workflows/release_tag.yml at lines 23-29, disable persisted credentials
and configure the authenticated origin immediately before the later tag push
using the bot token; in .github/workflows/release_publish.yml at lines 23-27,
disable persisted credentials since no git push occurs and the release action
supplies its token explicitly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 99f85e54-97a9-4d71-8be2-2985a2051c5b

📥 Commits

Reviewing files that changed from the base of the PR and between ab4b1fb and 7a2ae4a.

📒 Files selected for processing (8)
  • .github/workflows/release_publish.yml
  • .github/workflows/release_tag.yml
  • STYLE_GUIDE.md
  • apps/design_system_gallery/lib/components/message/stream_message_content.dart
  • apps/design_system_gallery/lib/components/reaction/stream_reactions.dart
  • packages/stream_core_flutter/CHANGELOG.md
  • packages/stream_core_flutter/lib/src/components/reaction/stream_reactions.dart
  • packages/stream_core_flutter/test/components/reaction/stream_reactions_test.dart

Comment thread .github/workflows/release_publish.yml Outdated
xsahil03x and others added 15 commits July 30, 2026 18:36
Extract the tag/publish commands from the workflows into `release:tag`,
`release:pub:dry`, and `release:pub` melos scripts (matching the existing
`lint:pub` convention) so they're documented in one place and runnable
locally. Workflows now call `melos run …`.

All three scripts publish/tag in dependency order (`--order-dependents`),
so a dependency is always tagged/published before anything that depends
on it — correct even when `release:pub` is run unscoped. The publish
workflow scopes each run to one package via the MELOS_PACKAGES env var
(melos's `--scope` flag can't read an env var from a script).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port of stream-chat-flutter release-pr skill, adapted for per-package
independent versioning and hand-curated changelogs (no melos version).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Repurpose the existing lint:pub to chat's exact pre-publish dry-run
command and make release:pub byte-identical (`-f`); drop the redundant
release:pub:dry. release_publish now runs `melos run lint:pub` for the
dry run. release:tag stays (no chat equivalent — per-package tagging).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… in skill

Multi-package release PRs use a generic `chore(repo): release packages` title
(short regardless of package count) instead of enumerating each pkg+version.
The release-pr skill now derives packages from `packages/*` and scopes from
pr_title.yml instead of a hard-coded table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dependency gate now polls `melos list --published --scope=$dep`
(melos's own published detection, same as --no-published) instead of
curling the pub.dev API. Drops the hardcoded URL and the pubspec version
parsing; melos checks the workspace version directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses CodeRabbit/zizmor findings:
- Bound the tag version capture to a semver shape (was an open-ended `.+`).
- Pass parsed package/version to the gate and changelog steps via step-level
  env vars instead of interpolating ${{ }} into the run: scripts (avoids
  template injection; dry-run/publish already used env).
- persist-credentials: false on checkout — this job never pushes, so the bot
  token no longer sits in git config through the melos/pub steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The checkout is read-only (only the Release step pushes, with its own
token), so drop the bot PAT from it entirely — the default GITHUB_TOKEN
clones fine. Combined with persist-credentials: false, the bot PAT never
enters this job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep the bot PAT and default persist-credentials on the checkout to
match stream-chat-flutter, reverting the persist-credentials/token
change. The regex tightening and env-var scoping (no ${{ }} in run:)
stay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Before publishing, check the per-version pub.dev endpoint
(/api/packages/$PKG/versions/$VERSION) and skip the publish step if the
version is already live. melos --no-published (which release:pub uses)
reads the package listing, which lags for minutes after a publish, so
re-running a finished publish could wrongly retry and fail "already
exists". The version endpoint stays fresh, so re-runs are now a clean
no-op while the CHANGELOG/GitHub Release steps still run.

Validated end-to-end on a real per-package test repo: a fresh publish
proceeds; re-running a published tag skips publish and exits 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-runs of an already-published tag rely on melos --no-published (like
stream-chat-flutter and the melos-action ecosystem). The guard only
helped when manually re-running an already-succeeded publish — an
uncommon, self-inflicted case where the failure is safe (pub.dev rejects
the duplicate; nothing double-publishes). Keeping the workflow minimal
and matching the reference repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation

Replace the bespoke "wait until in-workspace deps are on pub.dev" gate
step with `flutter pub publish --skip-validation` on the publish command
— Dart's recommended approach for publishing interdependent packages.
A package no longer blocks on its dependency being indexed; the
dependent resolves once the dependency's own run lands moments later.

Removes the melos --graph/awk/jq parsing and the melos --published poll,
whose CI listing-lag made coordinated releases slow (~75s+/level) and
brittle. Validated end-to-end on a per-package test repo: chain and
fan-out releases publish immediately with no failures.

Tradeoff: if a dependency's own publish fails, the dependent is
momentarily unresolvable until the dependency is re-published (safe,
idempotent re-run). Documented in STYLE_GUIDE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align the release-notes step with bluefireteam/melos-action:
- resolve the package dir via `melos list --json` instead of assuming
  `packages/<name>` (drops the dir==name assumption).
- match the `## <version>` heading by version token ($2==ver), so a
  dated heading like `## 0.4.0 (2026-07-31)` is still found.
- append a "Published to pub.dev: <link>" footer to the release body.

Verified on the test repo: a dated-header release extracts correctly and
links to the published version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Publishing interdependent packages with --skip-validation would let a
dependent publish before its dependency is live on pub.dev, leaving it
momentarily unresolvable if the dependency's own run fails. Restore the
"Wait for in-workspace dependencies" step: it polls pub.dev's fresh
per-version endpoint until each dependency is live before publishing, so
a dependent never publishes against a missing dependency. release:pub
drops --skip-validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x marked this pull request as ready for review July 31, 2026 02:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
.github/workflows/release_publish.yml (1)

111-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Split the overlong shell statements.

Lines 111 and 146 exceed 120 characters. Split the timeout error branch and construct the changelog URL in a separate variable.

As per coding guidelines, **/*.{dart,yaml} files must use a maximum line width of 120 characters.

Also applies to: 146-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release_publish.yml at line 111, Split the overlong
timeout error branch near the dependency wait logic into separate shell
statements while preserving its error message and exit behavior. At the
changelog-generation logic near the corresponding URL construction, assign the
URL to a separate variable before using it, keeping every YAML/Dart line within
120 characters.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/release-pr/SKILL.md:
- Line 65: Update the release workflow instructions around the branch update and
staging checks to use git status --short, not git status --short -uno. Check for
any tracked or untracked changes before checkout and again immediately before
git add -A, and stop the release process when either check is non-empty.

In @.github/workflows/release_publish.yml:
- Line 110: Update the pub.dev probe in the until loop to pass curl connection
and total-transfer time limits using --connect-timeout and --max-time, ensuring
each request returns within a bounded duration so the existing 15-minute expiry
check can execute.
- Around line 6-7: Update the release tag patterns in the workflow to recognize
Dart build-metadata versions with a literal “+” suffix, escaping the plus for
GitHub Actions glob syntax. Add the corresponding +<build> parse-regex branch
and matching trigger pattern in release_tag.yml, alongside the existing stable
and prerelease patterns.

In `@melos.yaml`:
- Around line 121-124: Update the tag creation command in the melos exec release
flow to check whether an existing $MELOS_PACKAGE_NAME-v$MELOS_PACKAGE_VERSION
tag resolves to HEAD; allow the command to succeed only when it already points
to HEAD or can be created for HEAD, and fail otherwise. Remove the unconditional
`|| true` suppression while preserving the subsequent push behavior.

---

Nitpick comments:
In @.github/workflows/release_publish.yml:
- Line 111: Split the overlong timeout error branch near the dependency wait
logic into separate shell statements while preserving its error message and exit
behavior. At the changelog-generation logic near the corresponding URL
construction, assign the URL to a separate variable before using it, keeping
every YAML/Dart line within 120 characters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 88aece31-8308-45ee-aa41-4d99e6226d8a

📥 Commits

Reviewing files that changed from the base of the PR and between 7a2ae4a and 3cfddec.

📒 Files selected for processing (5)
  • .claude/skills/release-pr/SKILL.md
  • .github/workflows/release_publish.yml
  • .github/workflows/release_tag.yml
  • STYLE_GUIDE.md
  • melos.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • STYLE_GUIDE.md

Comment thread .claude/skills/release-pr/SKILL.md Outdated
Comment thread .github/workflows/release_publish.yml Outdated
Comment thread .github/workflows/release_publish.yml Outdated
Comment thread melos.yaml Outdated
xsahil03x and others added 5 commits July 31, 2026 04:16
…se skill

Address CodeRabbit review on #142:

- release_publish.yml: align the tag trigger with pub.dev's suggested OIDC
  pattern (`<pkg>-vX.Y.Z*`) and extend the parse regex to accept build
  metadata (`+…`) alongside pre-release suffixes. A `+build` release now
  fires the workflow and validates, rather than being silently skipped. Make
  pre-release detection ignore the build-metadata part.
- release-pr skill: pre-flight now requires `git status --short` clean
  *including untracked files*, so a stray local file can't enter the release
  commit via `git add -A`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address CodeRabbit review on #142:

- release_publish.yml: add --connect-timeout/--max-time to the dependency
  wait probe so a stalled transfer can't hang past the 15-minute deadline.
- release:tag: replace `git tag … || true` with tag_package.sh, which creates
  the tag at HEAD, no-ops if it already points at HEAD, and fails loudly if an
  existing tag points at a different commit (a stale/failed release) instead of
  silently re-pushing the wrong tree or skipping the publish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse release:tag's two exec passes into one that tags (via tag_package.sh)
then pushes each package in dependency order — matching chat's tag-then-push
shape. The stale-tag guard still runs first, and `&&` skips the push if it
fails, so a bad tag never gets pushed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move `git push` into the script so release:tag is a single script call per
package. The push is idempotent (no-op if origin already has the tag) and is
skipped when the stale-tag guard fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace tag_package.sh with melos-action's proven pattern: create tags with
`git tag … || true`, then push only tags that point at HEAD, one at a time.
Pushing only HEAD tags means a stale tag on an old commit (e.g. a prior release
that was tagged but never published) is simply ignored rather than failing the
whole run — no stop-the-line, while still emitting one tag event per package.
The publish-side dependency wait already handles ordering, so pushes need no
special order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x force-pushed the sahil/flu-636-automated-package-publishing-for-core branch from 9115c3d to 015a791 Compare July 31, 2026 02:42
Address review feedback:

- release:tag pushed via `git tag --points-at HEAD`, which git sorts
  lexicographically — so "dependency order" held only by the accident that
  `-` sorts before `_`. Push inside the ordered `melos exec` instead, so a
  dependency's tag is genuinely pushed (and published) before its dependent's.
  The docs' "tags push in dependency order" claim is now true.
- Document that tagging is state-derived: it tags every unpublished package,
  not just the ones a PR bumped — so keep version bumps to release PRs, and
  publish a brand-new package before releasing anything that depends on it
  (else the dependent's wait polls for a version that never lands).
- Make that wait's timeout message name the new-package cause instead of a
  bare "re-run once it's published".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x force-pushed the sahil/flu-636-automated-package-publishing-for-core branch from 5f5ba3a to bc02c18 Compare July 31, 2026 11:43

@renefloor renefloor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed this with the load-bearing melos assumptions verified locally against the melos source and this workspace. Overall a well-reasoned design — the per-package tag + dependency-wait approach is the right answer to "more independent than chat", and most of the tricky details hold up. Findings below, ranked.

Should fix

1. lint:pub is now a no-op — verified

Adding --no-published means it matches zero packages once versions are released, which is the steady state. All three current versions are live on pub.dev (stream_core 0.4.0, stream_core_flutter 0.4.1, stream_thumbnail 0.1.0):

$ melos exec -c 1 --no-published --no-private --order-dependents -- "echo HIT"
  └> RUNNING (in 0 packages)   # was 3 packages with the old filter
     └> SUCCESS

So the "melos run lint:pub passes for all three packages" line in the description is vacuously true, and the script loses its old role as a local "are these actually publishable?" check.

Suggestion: leave lint:pub unfiltered (all non-private packages) and let MELOS_PACKAGES do the scoping in the workflow. That's strictly better — the 🌵 Dry Run step then actually runs even on a re-run of an already-published tag, and pub publish -n doesn't check version collisions, so idempotency is unaffected.

2. The dependency wait includes dev-dependencies and private packages

melos list --graph emits allDependenciesInWorkspace, which is dependencies + devDependencies + dependencyOverrides (melos/lib/src/package.dart:874). Today the graph is stream_core_flutter: ["stream_core"], so it's fine — but the first time a publishable package takes a workspace dev-dependency on a private package (a test-utils package, or stream_thumbnail_example), the release blocks for 15 minutes polling a pub.dev URL that will never 200, then fails — for a dependency that publishing never needed in the first place.

melos list --json already carries "private": true|false; filtering the dep list on it (and ideally down to real dependencies only) closes this.

3. Nothing verifies the publish actually landed

Between 📢 Publish to pub.dev and 🚀 Create GitHub Release there's no check that the version is live. If the scoped release:pub filter matches zero packages for any reason, the step reports SUCCESS and you get a GitHub Release announcing a version that isn't on pub.dev.

The curl poll from the wait step is already written — reusing it once after publish makes that state unreachable.

Worth fixing

4. Silent skip on an existing tag

if git tag $MELOS_PACKAGE_NAME-v$MELOS_PACKAGE_VERSION 2>/dev/null; then git push ...; fi

swallows the failure, so a stale tag on an old commit drops a package from the release with a green run. An else echo "::warning ::..." costs nothing.

Related: there are currently no stream_core-v* tags at all in this repo — only stream_core_flutter-v*, stream_thumbnail-v0.1.0, and the legacy v0.x tags. So the "an existing tag fails git tag and is skipped" safety net that the release:tag description leans on doesn't exist for stream_core.

5. release_tag.yml has no workflow_dispatch

It's gated on a commit-message prefix that the skill itself calls "load-bearing". If a squash title gets edited on the way in, there's no supported recovery path — you'd tag by hand. release_publish.yml has that escape hatch; the tag job should have one too.

6. Skill doc bug

grep -A6 'semantic_changelog_update' .github/workflows/pr_title.yml

stops exactly at scopes: | and never prints the scope→path map the skill tells you to read. Needs -A12.

7. Skill title inconsistency

The header says chore(repo): release <...> for multi-package; step 5 and STYLE_GUIDE.md say chore(repo): release packages.

Nits

  • The parse step hardcodes packages/$pkg/pubspec.yaml and greps ^version:, while the notes and wait steps deliberately resolve location/version via melos list --json ("dir name may differ"). Both fail safe — just inconsistent.
  • 📚 Checkout branch in release_publish.yml uses the bot PAT it doesn't need: nothing is pushed, and the release step passes the token explicitly. Dropping it reduces PAT exposure in a job that also holds id-token: write.
  • actions/checkout@v6 here vs @v4 in every other workflow in this repo.
  • Unpinned flutter pub global activate melos is the repo-wide pattern, but the release path is the worst place for a surprise major — local dev resolves melos 6.3.3, CI would get 8.2.2 today.
  • STYLE_GUIDE.md: extra blank line before ## Where to look when you're stuck.

Verified correct

Checked these so they don't get re-litigated:

  • MELOS_PACKAGES composes with --no-published rather than replacing it — createWorkspace does packageFilters.copyWith(scope: ...). The scoping and idempotency claims hold, and MELOS_PACKAGES=stream_core does not match stream_core_flutter (exact glob).
  • --order-dependents is dependencies-first. sortPackagesForExecution builds layers from in-degree 0 and then .reversed; empirically stream_thumbnail → stream_core → stream_core_flutter. The "tags push in dependency order" claim is accurate.
  • \$MELOS_PACKAGE_NAME escaping in release:tag is correct on LinuxresolveEnvironmentVariableReferences returns the input unchanged off Windows and lets sh expand.
  • [[ -z "$deps" ]] && { ...; exit 0; } does not trip set -e when deps are present.
  • melos list --graph / --json write the update notice to stderr, so the awk guard is harmless either way.
  • Examples and the gallery are publish_to: 'none', so dropping --ignore="*example*" is safe; pubspec_overrides.yaml is gitignored so bootstrap's path overrides don't ship.
  • Dart-before-Flutter ordering for the OIDC token is right, and actionlint is clean on both workflows.
  • The skill's claims about pr_title.yml's release/-branch gate match check-changelog-placement.sh.

…rify, dispatch

From renefloor's review:

- lint:pub: drop `--no-published` so the dry run isn't a no-op in steady state;
  MELOS_PACKAGES scopes it in CI and `pub publish -n` doesn't check collisions.
- Wait step: skip private in-workspace deps (they never publish, so waiting on
  one would hang until timeout); url-encode a build-metadata '+'.
- Add a post-publish "Verify published" step so a no-op publish can't cut a
  GitHub Release for a version that isn't on pub.dev.
- release:tag: warn (`::warning`) when a stale tag causes a package to be
  skipped, instead of silently dropping it from the release.
- release_tag.yml: add workflow_dispatch recovery + widen the gate for it.
- Skill: `grep -A6` -> `-A12` (was cutting off the scope map); fix multi-package
  title to match STYLE_GUIDE (`chore(repo): release packages`).
- STYLE_GUIDE: drop a stray blank line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release_tag.yml:
- Around line 14-18: Prevent release publishing from non-default-branch commits
by adding a guard before the publish steps in
.github/workflows/release_publish.yml at lines 23-27 that resolves GITHUB_REF
and verifies the target commit is an ancestor of origin/main, refusing the
release otherwise; update .github/workflows/release_tag.yml at lines 14-18 only
as needed to ensure manually created tags are subject to this validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 671446c4-fdee-419b-9f70-9870e5afaad5

📥 Commits

Reviewing files that changed from the base of the PR and between 5f5ba3a and 69b418d.

📒 Files selected for processing (5)
  • .claude/skills/release-pr/SKILL.md
  • .github/workflows/release_publish.yml
  • .github/workflows/release_tag.yml
  • STYLE_GUIDE.md
  • melos.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • .claude/skills/release-pr/SKILL.md
  • STYLE_GUIDE.md
  • melos.yaml

Comment thread .github/workflows/release_tag.yml
xsahil03x and others added 6 commits July 31, 2026 18:14
…ublished

Re-running release_publish for an already-published tag hit `flutter pub publish`
"version already exists" because `melos --no-published` doesn't reliably exclude
published versions in CI. Collapse publish + verify into one step that checks the
live per-version endpoint first: skip if the version is already there (a true
no-op), otherwise publish and confirm it landed before the release is cut. This
makes workflow_dispatch recovery safe whether the prior run failed or succeeded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One tag = one package, and idempotency is now the pub.dev per-version check —
so the melos exec fan-out (--order-dependents / --no-private / --no-published)
added nothing. Publish the tagged package directly with `flutter pub publish
--force` in its directory, and drop the now-unused release:pub script.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror stream-chat-flutter: the tagging logic lives in the workflow, not a melos
script. Move the `melos exec … git tag/push` command straight into
release_tag.yml's run step and remove the now-single-use release:tag from
melos.yaml. Behaviour is unchanged (same command, same dependency-ordered
push + stale-tag ::warning); release:pub stays a melos script as in chat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Trim the verbose comments (tag step, dependency wait, dep resolution, gate) to
one or two terse lines each, and fix the release:pub / lint:pub descriptions —
release:pub no longer relies on --no-published for idempotency (the publish
step's live-check does), so the description no longer claims it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ame, docs

- release_publish checkout: use the default GITHUB_TOKEN (the job never pushes;
  the release step passes its own token), so no write-scoped PAT sits in git
  config across the melos install/bootstrap steps.
- Parse step: resolve the package's pubspec by grepping for its `name:` instead
  of assuming packages/<name>/ — robust to a dir that differs from the package
  name (melos isn't installed yet at this step).
- release_tag: drop the dead `git config user.*` — the tags are lightweight
  (`git tag <name>`), which record no tagger.
- Document that release PRs must be squash-merged (the tag gate reads the tip
  commit's message; a merge commit would silently bypass it).

Validated on the test repo: checkout with the default token, parse-by-name, and
tag-without-identity all pass; the manual workflow_dispatch recovery path runs
green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants