Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a460b6c
feat(reaction): report the pressed item from StreamReactions
xsahil03x Jul 29, 2026
9b8ce39
Merge remote-tracking branch 'origin/main' into feat/reactions-on-rea…
xsahil03x Jul 29, 2026
faab75d
chore(reaction): order key first in StreamReactionsItem
xsahil03x Jul 29, 2026
ad009d1
chore(reaction): hoist _colorScheme in _StreamReactionsThemeDefaults
xsahil03x Jul 29, 2026
8d0109d
feat(reaction): add OnReactionItemPressed typedef; showcase onReactio…
xsahil03x Jul 29, 2026
77d1cb7
docs(reaction): match sibling typedef doc style for OnReactionItemPre…
xsahil03x Jul 29, 2026
3e23631
feat(reaction): deprecate StreamReactions.onPressed instead of removi…
xsahil03x Jul 30, 2026
40ccd8a
refactor(reaction): drop the deprecated_member_use ignores
xsahil03x Jul 30, 2026
7e09f97
Merge remote-tracking branch 'origin/main' into feat/reactions-on-rea…
xsahil03x Jul 30, 2026
7a2ae4a
ci(repo): add automated per-package pub.dev publishing
xsahil03x Jul 30, 2026
efa3eb4
refactor(repo): move release commands into melos.yaml scripts
xsahil03x Jul 30, 2026
ad42bce
docs(repo): trim release workflow comments to essentials
xsahil03x Jul 30, 2026
175befd
Merge remote-tracking branch 'origin/main' into sahil/flu-636-automat…
xsahil03x Jul 30, 2026
824c623
chore(repo): add release-pr skill
xsahil03x Jul 30, 2026
d745aa3
refactor(repo): align pub scripts with stream-chat-flutter
xsahil03x Jul 30, 2026
6b622b1
docs(repo): make multi-package release title generic; derive packages…
xsahil03x Jul 30, 2026
4991589
refactor(repo): gate on melos --published instead of a raw pub.dev curl
xsahil03x Jul 30, 2026
26ad40c
ci(repo): harden release_publish parsing and token exposure
xsahil03x Jul 30, 2026
416063b
ci(repo): use default GITHUB_TOKEN for release_publish checkout
xsahil03x Jul 30, 2026
cc93c5f
ci(repo): match chat checkout in release_publish
xsahil03x Jul 30, 2026
0cea5a0
fix(repo): make publish idempotent with a pub.dev-version guard
xsahil03x Jul 31, 2026
72f832c
revert: drop the pub.dev-version publish guard
xsahil03x Jul 31, 2026
3d512d4
refactor(repo): drop the dependency gate for pub publish --skip-valid…
xsahil03x Jul 31, 2026
67283e2
refactor(repo): harden changelog extraction (melos path + token match)
xsahil03x Jul 31, 2026
f1daa5d
refactor(repo): restore dependency wait instead of --skip-validation
xsahil03x Jul 31, 2026
3cfddec
docs(repo): drop redundant --no-published comment
xsahil03x Jul 31, 2026
1f9da35
ci(repo): support build-metadata tags; catch untracked files in relea…
xsahil03x Jul 31, 2026
d404903
ci(repo): bound pub.dev probes and guard stale release tags
xsahil03x Jul 31, 2026
b385c50
refactor(repo): tag and push in one melos exec pass
xsahil03x Jul 31, 2026
77c8d84
refactor(repo): push the tag inside tag_package.sh
xsahil03x Jul 31, 2026
015a791
refactor(repo): push only HEAD tags instead of guarding stale tags
xsahil03x Jul 31, 2026
bc02c18
ci(repo): push tags in dependency order; document state-derived tagging
xsahil03x Jul 31, 2026
69b418d
ci(repo): address review — meaningful dry-run, dep filter, publish ve…
xsahil03x Jul 31, 2026
fb75087
ci(repo): make publish idempotent via pub.dev state, not melos --no-p…
xsahil03x Jul 31, 2026
007c966
ci(repo): publish directly instead of via melos run release:pub
xsahil03x Jul 31, 2026
6342e3a
Revert "ci(repo): publish directly instead of via melos run release:pub"
xsahil03x Jul 31, 2026
10df94b
ci(repo): inline tagging into release_tag.yml, drop release:tag script
xsahil03x Jul 31, 2026
7440628
docs(repo): tighten release-workflow comments
xsahil03x Jul 31, 2026
998e449
ci(repo): address self-review — drop unused PAT, resolve pubspec by n…
xsahil03x Jul 31, 2026
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
167 changes: 167 additions & 0 deletions .claude/skills/release-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
name: release-pr
description: >
Open a release PR for stream-core-flutter: bump the version(s) of one or more packages, finalise their
hand-curated CHANGELOGs (promote `## Upcoming` → `## X.Y.Z`), and open a PR from a `release/` branch. Per-package
independent versioning — release one package or several in a single PR.
disable-model-invocation: true
argument-hint: "[<package> <version> ...]"
arguments: [packages]
allowed-tools:
- Bash(git *)
- Bash(gh *)
- Bash(melos *)
- Bash(which *)
- Bash(grep *)
- Bash(sed *)
- Read
- Edit
- Write
---

# release-pr

Opens a release PR for stream-core-flutter. Branch `release/<...>` → base `main` → title
`chore(<scope>): release <package> vX.Y.Z` (single package) or `chore(repo): release packages` (multiple).

**This skill only opens the PR.** After merge, tagging and pub.dev publishing are automatic:
[`release_tag.yml`](../../../.github/workflows/release_tag.yml) tags every bumped package (`<pkg>-vX.Y.Z`) and
[`release_publish.yml`](../../../.github/workflows/release_publish.yml) publishes each and cuts a GitHub Release.
See the "Releasing" section of `STYLE_GUIDE.md`.

## Key facts for this repo

- **Independent per-package versioning.** Each package releases on its own tag `<pkg>-vX.Y.Z`. A single release PR
may bump **one package or several** — each still gets its own tag + publish run.
- **CHANGELOGs are hand-curated.** Never run `melos version` — it regenerates changelog entries from commit messages
and clobbers the curated `## Upcoming` bullets. Releasing means *promoting* the existing `## Upcoming` heading to
`## X.Y.Z`, not rewriting it.
- **`release/` branch is required**, not a convention: the changelog-placement check in `pr_title.yml` only allows a
`## Upcoming` heading to become `## X.Y.Z` on a `release/` branch.

Publishable packages are the non-private ones under `packages/*` — list them with
`melos list --no-private`. Their conventional-commit scopes are defined in
`.github/workflows/pr_title.yml` (the `semantic_changelog_update` job maps each
scope to a package path); read that map rather than hard-coding it, so adding a
package needs no change here:

```bash
grep -A12 'semantic_changelog_update' .github/workflows/pr_title.yml
```

## Inputs

1. **Which packages + versions.** If given as args (e.g. `/release-pr stream_core 0.4.1 stream_core_flutter 0.5.0`),
use them; strip any leading `v`. Otherwise **detect and confirm**: a package needs releasing when its
`CHANGELOG.md` has a non-empty `## Upcoming` section. List those and ask the user for each new version (they pick
the semver bump; don't infer it).
2. **Base branch** is always `main`.

## Pre-flight

Run these. **If any fails, stop, surface it to the user, and do not auto-fix** (no stashing, no force-pull, no
killing processes).

- `git checkout main && git pull --ff-only` leaves `git status --short` clean — **including untracked files**, so a
stray local file can't slip into the release commit at `git add -A` (step 5).
- `which melos`, `gh auth status` succeed.
- Latest CI on `main` is green: `gh run list --branch main --limit 5` — no failures on the most recent runs.
- No open release PR for the same branch: `gh pr list --head <branch> --state all --json number` returns `[]`.

## Steps

### 1. Branch off main

```bash
git checkout -b <branch>
```

Branch name: `release/<pkg>-vX.Y.Z` for a single package, or `release/YYYY-MM-DD` for a multi-package release.

### 2. Bump version(s)

For **each** package being released:

- Set `version: <newver>` in `packages/<pkg>/pubspec.yaml`.

Only if a released package is a **dependency that a dependent must now require at the new version** (the dependent
started using a new API), also bump that package's entry in `melos.yaml`'s `command.bootstrap.dependencies` block
(`grep -nE 'stream_(core|core_flutter|thumbnail):' melos.yaml`) — and release the dependent too. A compatible bump
that the existing caret already allows (e.g. `stream_core 0.4.0 → 0.4.1` under `stream_core: ^0.4.0`) needs **no**
block change.

Then propagate constraints:

```bash
melos bootstrap
```

Do **not** run `melos version`.

### 3. Finalise each released package's CHANGELOG

For every package being released, in `packages/<pkg>/CHANGELOG.md` rename the top `## Upcoming` heading to
`## <newver>`. Keep the curated bullets exactly as they are — do not add, rewrite, or regenerate them. Sub-headings
(`### ✨ Features`, `### 🐛 Bug Fixes`, `### 🛑 Breaking / Removals`) stay untouched.

If a package is being released only because a dependency bump forces it (no user-facing change of its own), give it a
`## <newver>` section with a single bullet noting the dependency bump — every released package must have a non-empty
`## <newver>` section (pana fails on an empty or missing one).

Do not hand-write cross-package "bumps stream_core to X.Y.Z" lines beyond that; per `STYLE_GUIDE.md`, cross-linking is
the release tooling's job.

### 4. Sanity-check

```bash
melos run analyze
melos run lint:pub
```

If either fails, surface it and stop.

### 5. Commit and push

```bash
git add -A
git commit -m "<title>"
git push -u origin <branch>
```

Single commit. **The title is load-bearing** — `release_tag.yml` gates on the `chore(...): release` prefix:

- One package: `chore(<scope>): release <package> vX.Y.Z` (e.g. `chore(llc): release stream_core v0.4.1`).
- Several: `chore(repo): release packages` — generic, so the title stays short no matter how many packages bump.

Tagging derives from package state, not this title, so a typo can't mis-tag — but keep the prefix intact or the tag
job won't fire.

### 6. Open the PR

Build the body from the promoted CHANGELOG sections (the same content that becomes each GitHub Release). Do **not**
use `gh api .../generate-notes` — this repo deliberately does not use GitHub's generated notes.

```bash
gh pr create --base main --head <branch> --title "<title>" --body-file <notes>
```

A good body lists each released package, its version, and its `## <newver>` CHANGELOG section. Return the PR URL.

**Tell the user to squash-merge it.** `release_tag.yml` gates on the *tip* commit's message, so a squash lands the
`chore(...): release` title as that commit. A merge commit would make the tip `Merge pull request #…` and the release
would silently not run.

## After merge (FYI)

`release_tag.yml` tags every bumped package and `release_publish.yml` publishes each (OIDC) and creates a per-package
GitHub Release from its CHANGELOG section. Multi-package releases publish in dependency order automatically (the
publish job waits for in-workspace dependencies to be live first).

## Don't

- **Never run `melos version`** — it clobbers the hand-curated CHANGELOGs.
- **Never tag or push a tag** — `release_tag.yml` does it on merge.
- **Never run `melos run release:pub` locally** — it's the CI publish step; running it publishes from an unreviewed
tree. Refuse even if asked. (Tagging is inlined in `release_tag.yml`, not a melos script — don't run it by hand.)
- **Never create a GitHub release** (`gh release create`) — `release_publish.yml` creates it after the tag is pushed.
- **Never merge the PR.** Return the URL and stop.
206 changes: 206 additions & 0 deletions .github/workflows/release_publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
name: release_publish

on:
push:
tags:
# <pkg>-vX.Y.Z plus any pre-release (-…) or build (+…) suffix — matches
# pub.dev's suggested OIDC tag pattern; the parse step validates the rest.
- '*-v[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch: # manual re-runs against a tag ref

concurrency:
# false: don't let a re-run cancel an in-flight publish of the same tag.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
permissions:
contents: write # create the GitHub Release
id-token: write # OIDC auth to pub.dev
runs-on: ubuntu-latest
steps:
- name: 📚 Checkout branch
# Default GITHUB_TOKEN: this job never pushes to git (the release step
# passes its own token), so no bot PAT sits in git config during setup.
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: 🏷️ Parse package and version from tag
id: parse
shell: bash
run: |
set -euo pipefail

ref="${GITHUB_REF#refs/tags/}"
echo "📦 Tag: $ref"

# <package>-v<semver>, with optional pre-release (-…) and build (+…) suffixes.
if [[ ! "$ref" =~ ^([a-z0-9_]+)-v([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$ ]]; then
echo "::error ::Tag '$ref' does not match '<package>-v<semver>'."
exit 1
fi

pkg="${BASH_REMATCH[1]}"
version="${BASH_REMATCH[2]}"

# Find the package's pubspec by name — melos isn't installed yet, and a
# package's directory may differ from its name.
pubspec="$(grep -rlE "^name:[[:space:]]+$pkg\$" --include=pubspec.yaml packages/ 2>/dev/null | head -n1)"
if [[ -z "$pubspec" ]]; then
echo "::error ::No package named '$pkg' found under packages/."
exit 1
fi

# Guard a stray tag: pubspec version must equal the tag version.
pubspec_version="$(grep -E '^version:' "$pubspec" | head -n1 | sed -E 's/^version:[[:space:]]*//')"
if [[ "$pubspec_version" != "$version" ]]; then
echo "::error ::Tag version ($version) does not match $pubspec version ($pubspec_version)."
exit 1
fi

# Pre-release = a hyphen suffix, ignoring any build-metadata (+…) part.
is_prerelease=$([[ "${version%%+*}" == *-* ]] && echo true || echo false)

{
echo "package=$pkg"
echo "version=$version"
echo "prerelease=$is_prerelease"
} >> "$GITHUB_OUTPUT"

- name: 🎯 Setup Dart
# Before Flutter: provides the pub.dev OIDC token.
uses: dart-lang/setup-dart@v1

- name: 🐦 Install Flutter
uses: subosito/flutter-action@v2
with:
channel: stable

- name: 📦 Install Tools
run: flutter pub global activate melos

- name: 🔧 Bootstrap Workspace
run: melos bootstrap --verbose

- name: 🌵 Dry Run
# MELOS_PACKAGES scopes lint:pub to the tagged package.
run: melos run lint:pub
env:
MELOS_PACKAGES: ${{ steps.parse.outputs.package }}

- name: ⏳ Wait for in-workspace dependencies
# A dependent can reach pub.dev before its dependency is indexed (each
# runs separately), which the server rejects. Wait via the per-version
# endpoint — fresh in CI, unlike the listing `melos --published` reads.
shell: bash
env:
PKG: ${{ steps.parse.outputs.package }}
run: |
set -euo pipefail

# Deps from the graph, versions from list --json. Skip private deps —
# they never publish, so waiting on one would hang until timeout.
info="$(melos list --json 2>/dev/null)"
raw="$(melos list --graph 2>/dev/null | awk '/^\{/{f=1} f{print} /^\}$/{f=0}' \
| jq -r --arg p "$PKG" '.[$p] // [] | .[]')"

deps=""
for dep in $raw; do
priv="$(printf '%s' "$info" | jq -r --arg n "$dep" '.[] | select(.name==$n) | .private')"
if [ "$priv" != "true" ]; then deps="$deps $dep"; fi
done

[[ -z "${deps// /}" ]] && { echo "✅ $PKG has no publishable in-workspace dependencies."; exit 0; }

for dep in $deps; do
want="$(printf '%s' "$info" | jq -r --arg n "$dep" '.[] | select(.name==$n) | .version')"
enc="${want//+/%2B}" # url-encode a build-metadata '+'
echo "⏳ Waiting for $dep v$want on pub.dev…"
deadline=$((SECONDS + 900)) # 15 minutes
until curl -sfL --connect-timeout 10 --max-time 30 -o /dev/null "https://pub.dev/api/packages/$dep/versions/$enc"; do
(( SECONDS < deadline )) || { echo "::error ::Timed out after 15m waiting for $dep v$want on pub.dev. If $dep is a new package, publish it (enable automated publishing) before releasing packages that depend on it; otherwise re-run this once $dep v$want is live."; exit 1; }
echo " …not live yet; retrying in 15s"
sleep 15
done
echo "✅ $dep v$want is live."
done

- name: 📢 Publish to pub.dev
# Idempotent by pub.dev state, not `melos --no-published` (which lags in
# CI): skip if the version is already live, else publish and confirm it
# landed before the release is cut.
shell: bash
env:
PKG: ${{ steps.parse.outputs.package }}
VERSION: ${{ steps.parse.outputs.version }}
MELOS_PACKAGES: ${{ steps.parse.outputs.package }}
run: |
set -euo pipefail
enc="${VERSION//+/%2B}" # url-encode a build-metadata '+'
url="https://pub.dev/api/packages/$PKG/versions/$enc"

# Already live? Nothing to do — a genuinely idempotent re-run.
if curl -sfL --connect-timeout 10 --max-time 30 -o /dev/null "$url"; then
echo "✅ $PKG v$VERSION is already on pub.dev; nothing to publish."
exit 0
fi

echo "📦 Publishing $PKG v$VERSION…"
melos run release:pub

# Confirm it actually landed before the release is cut (guards a no-op
# publish from producing a Release for a version that isn't on pub.dev).
echo "⏳ Confirming $PKG v$VERSION is live…"
deadline=$((SECONDS + 300)) # 5 minutes
until curl -sfL --connect-timeout 10 --max-time 30 -o /dev/null "$url"; do
(( SECONDS < deadline )) || { echo "::error ::$PKG v$VERSION did not appear on pub.dev after publishing."; exit 1; }
echo " …not live yet; retrying in 10s"
sleep 10
done
echo "✅ $PKG v$VERSION is live on pub.dev."

- name: 📝 Extract CHANGELOG section
id: notes
shell: bash
env:
PKG: ${{ steps.parse.outputs.package }}
VERSION: ${{ steps.parse.outputs.version }}
run: |
set -euo pipefail

# Resolve location from melos (dir name may differ from package name).
pkg_path="$(melos list --json 2>/dev/null | jq -r --arg n "$PKG" '.[] | select(.name==$n) | .location')"
rel_path="${pkg_path#"$GITHUB_WORKSPACE"/}"
changelog="$pkg_path/CHANGELOG.md"
notes_file="$RUNNER_TEMP/release_notes.md"

# Match the version *token* ($2) so a dated heading still matches and
# "0.4.0" never matches "0.4.00".
if [[ -f "$changelog" ]]; then
awk -v ver="$VERSION" '/^## /{flag=($2==ver); next} flag' "$changelog" > "$notes_file"
fi

# Publish is irreversible; never fail the release on a missing heading.
if [[ ! -s "$notes_file" ]]; then
echo "See [CHANGELOG](https://github.com/${GITHUB_REPOSITORY}/blob/${GITHUB_REF_NAME}/$rel_path/CHANGELOG.md)." > "$notes_file"
fi

{
echo ""
echo "---"
echo "Published to pub.dev: https://pub.dev/packages/$PKG/versions/$VERSION"
} >> "$notes_file"

echo "path=$notes_file" >> "$GITHUB_OUTPUT"

- name: 🚀 Create GitHub Release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
name: ${{ steps.parse.outputs.package }} v${{ steps.parse.outputs.version }}
body_path: ${{ steps.notes.outputs.path }}
prerelease: ${{ steps.parse.outputs.prerelease }}
make_latest: false # independent versioning: no single "latest" release
token: ${{ secrets.BOT_GITHUB_API_TOKEN }}
Loading
Loading