From 38a0cf1de779e09afaeb0f45fa8965bf25477dc8 Mon Sep 17 00:00:00 2001 From: VelikovPetar Date: Mon, 2 Nov 2026 15:02:48 +0100 Subject: [PATCH] ci(repo): support a second publishable package released in lockstep Prepare CI and release tooling for stream_chat_flutter_ai_ondevice, which ships with the same version as stream_chat_flutter_ai from one v tag. Everything iterates over the publishable packages under packages/, so it works before the ondevice package lands. - pr_title: add the `ondevice` scope, mapped to its CHANGELOG. - release_tag / release_publish: verify every publishable pubspec matches the tag, and that lockstep constraints are ^X.Y.Z (new verify-release-versions.sh); dry-run every publishable package. - release:pub: fail fast and retry each upload once after a minute, since a dependent needs its sibling's new version on pub.dev. - package_analysis: analyze and test each package under packages/. - release-pr skill and CLAUDE.md: describe lockstep releases. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/release-pr/SKILL.md | 93 ++++++++++++------- .github/actions/package_analysis/action.yml | 17 +++- .github/workflows/pr_title.yml | 8 +- .github/workflows/release_publish.yml | 19 ++-- .github/workflows/release_tag.yml | 12 +-- .../scripts/verify-release-versions.sh | 60 ++++++++++++ CLAUDE.md | 42 ++++++--- pubspec.yaml | 10 +- 8 files changed, 189 insertions(+), 72 deletions(-) create mode 100755 .github/workflows/scripts/verify-release-versions.sh diff --git a/.claude/skills/release-pr/SKILL.md b/.claude/skills/release-pr/SKILL.md index 6d6cd39..0fd2c68 100644 --- a/.claude/skills/release-pr/SKILL.md +++ b/.claude/skills/release-pr/SKILL.md @@ -1,8 +1,9 @@ --- name: release-pr description: > - Open a release PR for stream_chat_flutter_ai: bump the package version and the README install snippets, finalise - the CHANGELOG, and open a PR against main with auto-generated release notes. + Open a release PR for stream_chat_flutter_ai and stream_chat_flutter_ai_ondevice, released in lockstep: bump both + package versions, the lockstep constraint and the README install snippets, finalise both CHANGELOGs, and open a PR + against main with auto-generated release notes. disable-model-invocation: true argument-hint: "[version]" arguments: [version] @@ -22,7 +23,7 @@ allowed-tools: # release-pr -Opens a release PR for stream_chat_flutter_ai. Branch `release/v` → base `main` → title +Opens a release PR for the repo's publishable packages. Branch `release/v` → base `main` → title `chore(repo): release v`. **This skill only opens the PR.** After merge, tagging and pub.dev publishing are automatic: @@ -35,11 +36,16 @@ per [Choosing the version](#choosing-the-version). ## Key facts for this repo -- **One publishable package:** `packages/stream_chat_flutter_ai`. The example app is `publish_to: none`, and the - workspace root is private. Nothing depends on the package inside the repo except the example, which uses - `any`, so there are no intra-repo constraints to bump. +- **Two publishable packages, released in lockstep:** `packages/stream_chat_flutter_ai` (core) and + `packages/stream_chat_flutter_ai_ondevice`. They always share one `version:`, and one `v` tag publishes + both — so both are bumped even when only one has changes. The ondevice package depends on + `stream_chat_flutter_ai: ^`; that constraint lives in the root `melos.command.bootstrap.dependencies` + list, and bootstrap copies it into every member that declares it. The example apps and the workspace root are + `publish_to: none`. **If `packages/stream_chat_flutter_ai_ondevice` doesn't exist yet, skip every ondevice step + below** — the workflows skip it too. - **The title is load-bearing.** `release_tag.yml` only fires on a tip commit starting with - `chore(repo): release v`, and refuses to tag unless the version in it matches the pubspec. `repo` is also the + `chore(repo): release v`, and refuses to tag unless the version in it matches every publishable pubspec (and the + lockstep constraint is `^`). `repo` is also the scope `pr_title.yml` doesn't map to a CHANGELOG, so the changelog check doesn't apply — this skill edits the CHANGELOG anyway. - **The CHANGELOG is hand-curated.** Never run `melos version` — it regenerates entries from commit messages and @@ -60,18 +66,20 @@ per [Choosing the version](#choosing-the-version). Tags, not `gh release list`: a tag can exist without a GitHub Release. Empty output means there is no previous tag (true for the first automated release — 0.1.0 was published by hand and never tagged). -A release is warranted when `packages/stream_chat_flutter_ai/CHANGELOG.md` has a non-empty `## Upcoming` section. -If it doesn't, say so and stop — there is nothing to release. +A release is warranted when either package's `CHANGELOG.md` has a non-empty `## Upcoming` section. If neither +does, say so and stop — there is nothing to release. ## Choosing the version Two steps: classify the release from the CHANGELOG, then map that onto the current version. -**1. Classify.** Read the curated `## Upcoming` section — the same bullets that become the release notes, so they -are the authority on what the release contains: +**1. Classify.** Read both curated `## Upcoming` sections — the same bullets that become the release notes, so +they are the authority on what the release contains. The packages share one version, so the release is classified +by the more severe of the two: ```bash sed -n '/^## Upcoming/,/^## [0-9]/p' packages/stream_chat_flutter_ai/CHANGELOG.md +sed -n '/^## Upcoming/,/^## [0-9]/p' packages/stream_chat_flutter_ai_ondevice/CHANGELOG.md ``` The sub-headings are plain emoji lines, not markdown headings: `💥 Breaking`, `✅ Added`, `🔄 Changed`, @@ -91,9 +99,11 @@ releases shift down one slot. A release with no public API change still bumps th number: the release workflows only handle `X.Y.Z` versions, so a `+1` version can't be tagged or published. ```bash -grep '^version:' packages/stream_chat_flutter_ai/pubspec.yaml +grep '^version:' packages/*/pubspec.yaml ``` +Both lines must show the same version. If they don't, the lockstep is already broken — surface it and stop. + | Release | at/above `1.0.0` | below `1.0.0` | | --- | --- | --- | | breaking | major | minor | @@ -123,12 +133,12 @@ stashing uncommitted work, no force-pulling, no killing processes). - **No `git:` or `path:` entry under `dependencies`** — pub.dev rejects both: ```bash - grep -n -B2 -A4 -E '^\s+(git|path):' pubspec.yaml packages/stream_chat_flutter_ai/pubspec.yaml + grep -n -B2 -A4 -E '^\s+(git|path):' pubspec.yaml packages/*/pubspec.yaml ``` An entry under `dev_dependencies` is fine — pub ignores those when publishing. Only a real `dependencies` - entry (in the package, or in the root `melos.command.bootstrap.dependencies` block that bootstrap copies into - it) blocks. Surface it and stop — don't pick the constraint yourself. Once the user gives you one, set it in + entry (in a package, or in the root `melos.command.bootstrap.dependencies` block that bootstrap copies into + them) blocks. Surface it and stop — don't pick the constraint yourself. Once the user gives you one, set it in the root `pubspec.yaml` block only and let bootstrap propagate it in step 2. ## Steps @@ -145,15 +155,19 @@ git checkout -b release/v Edit by hand: -- **`packages/stream_chat_flutter_ai/pubspec.yaml`** — set `version: `. This is the package's own - version, the one thing in it bootstrap doesn't manage. Change nothing else. -- **The install snippets** — `stream_chat_flutter_ai: ^` in `README.md` and - `packages/stream_chat_flutter_ai/README.md` (find them with - `grep -n 'stream_chat_flutter_ai: \^' README.md packages/stream_chat_flutter_ai/README.md`). The package README - is what pub.dev shows, so a stale caret there tells new users to install the old version. +- **Both package pubspecs** — set `version: ` in `packages/stream_chat_flutter_ai/pubspec.yaml` and + `packages/stream_chat_flutter_ai_ondevice/pubspec.yaml`. This is each package's own version, the one thing in + it bootstrap doesn't manage. Change nothing else in them. +- **The lockstep constraint** — set `stream_chat_flutter_ai: ^` in the root `pubspec.yaml`, under + `melos.command.bootstrap.dependencies`. Don't hand-edit it in the ondevice pubspec; bootstrap copies it there. +- **The install snippets** — `stream_chat_flutter_ai: ^` and `stream_chat_flutter_ai_ondevice: ^` + in the root and both package READMEs: -Leave `packages/stream_chat_flutter_ai/example/pubspec.yaml` alone — it depends on `any` and resolves through the -workspace. + ```bash + grep -n -E 'stream_chat_flutter_ai(_ondevice)?: \^' README.md packages/*/README.md + ``` + + The package READMEs are what pub.dev shows, so a stale caret there tells new users to install the old version. Then run: @@ -167,7 +181,10 @@ Check the diff shape: git diff --stat ``` -Only `packages/stream_chat_flutter_ai/pubspec.yaml` and the two READMEs should appear (the CHANGELOG comes next). +Only these should appear (the CHANGELOGs come next): the root `pubspec.yaml`, the two package pubspecs, the +READMEs you edited, and any other member bootstrap copied the constraint into — every pubspec that declares +`stream_chat_flutter_ai` gets it, so an example's constraint can move too. That's fine: examples are private and +resolve through the workspace. `pubspec.lock` may show up too, but the bump itself never changes it — workspace members aren't listed in it. A lockfile diff means the local Flutter SDK resolved transitive packages differently from the one that wrote it (it @@ -177,8 +194,13 @@ something unexpected — surface it and stop. ### 3. Finalise the CHANGELOG -Rename `## Upcoming` to `## ` in `packages/stream_chat_flutter_ai/CHANGELOG.md`. Keep the curated bullets -and sub-headings exactly as they are — don't add, rewrite, reorder or regenerate them. +Rename `## Upcoming` to `## ` in both `packages/stream_chat_flutter_ai/CHANGELOG.md` and +`packages/stream_chat_flutter_ai_ondevice/CHANGELOG.md`. Keep the curated bullets and sub-headings exactly as they +are — don't add, rewrite, reorder or regenerate them. + +A package with no `## Upcoming` (or an empty one) still ships `` in lockstep, and pub.dev warns when the +CHANGELOG doesn't mention the published version. Give it a `## ` section with the single line +`- No changes; released in lockstep with .` Don't write fresh bullets from `git log`. An unreleased user-facing change that never got an `## Upcoming` bullet is a gap to raise with the user, not one for this step to fill in. @@ -193,7 +215,7 @@ If it fails, surface to the user and stop. ```bash git checkout pubspec.lock # drop local re-resolution churn (see step 2) -git status --short # exactly the pubspec, CHANGELOG and two READMEs; nothing untracked is release material +git status --short # exactly the step 2 files and both CHANGELOGs; nothing untracked is release material git add -u # tracked modifications only git commit -m "chore(repo): release v" ``` @@ -218,9 +240,10 @@ PR title as the tip commit; the title must therefore stay exactly `chore(repo): melos run lint:pub ``` -This is the real publish gate, and it now covers the package: `lint:pub` skips versions already on pub.dev, and -`` isn't. Confirm the output shows `stream_chat_flutter_ai` being validated — if melos reports no -packages, the version bump didn't take. Read failures carefully — pub reports two severities and only one blocks: +This is the real publish gate, and it now covers both packages: `lint:pub` skips versions already on pub.dev, and +`` isn't. Confirm the output shows both `stream_chat_flutter_ai` and `stream_chat_flutter_ai_ondevice` +being validated — if melos reports no packages, or only one, a version bump didn't take. The ondevice dry run +resolves core through the workspace, so it passes although core `` isn't on pub.dev yet. Read failures carefully — pub reports two severities and only one blocks: - **"Package validation found the following error"** — blocks. `release_publish.yml` runs `pub publish -f`, and `-f` does **not** bypass errors. Must be fixed before merge. @@ -274,10 +297,12 @@ Return the PR URL. **Tell the user to squash-merge it**, without editing the tit ## After merge (FYI) -`release_tag.yml` checks that `v` matches the pubspec and isn't tagged yet, then pushes the tag with the -bot PAT. `release_publish.yml` fires on that tag push, runs a dry run, then `melos run release:pub` (OIDC), and -creates the GitHub Release. The dry run passes on a version that's already live and `release:pub` skips it, so a -re-run after a failure is a clean no-op for anything already published. +`release_tag.yml` checks that `v` matches every publishable pubspec and isn't tagged yet, then pushes the +tag with the bot PAT. `release_publish.yml` fires on that tag push, runs a dry run of each package, then +`melos run release:pub` (OIDC), and creates the GitHub Release. `release:pub` publishes core first, because the +ondevice package needs core `` on pub.dev, and retries a failed upload once after a minute. The dry run +passes on a version that's already live and `release:pub` skips it, so a re-run after a failure (say, core published +but ondevice didn't) publishes only what's missing. ## Don't diff --git a/.github/actions/package_analysis/action.yml b/.github/actions/package_analysis/action.yml index 3cb93bd..917a370 100644 --- a/.github/actions/package_analysis/action.yml +++ b/.github/actions/package_analysis/action.yml @@ -11,11 +11,24 @@ runs: shell: bash run: melos bootstrap --verbose + # Every package directly under packages/ — the examples sit one level + # deeper, so the glob skips them. + # # Only analyze lib/; non-client code doesn't need to work on # all supported legacy versions. # # `--exclude-tags golden` is why this doesn't need the committed CI # goldens, which are pinned to the `test` job's Flutter version. - - name: "Stream Chat Flutter AI Analyze" + - name: "Analyze and Test Packages" shell: bash - run: cd packages/stream_chat_flutter_ai/lib && dart analyze --fatal-infos . && cd .. && flutter test --exclude-tags golden + run: | + set -euo pipefail + for pubspec in packages/*/pubspec.yaml; do + package=$(dirname "$pubspec") + echo "::group::$package" + (cd "$package" && dart analyze --fatal-infos lib) + if [[ -d "$package/test" ]]; then + (cd "$package" && flutter test --exclude-tags golden) + fi + echo "::endgroup::" + done diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml index 33a493c..cfc7ddc 100644 --- a/.github/workflows/pr_title.yml +++ b/.github/workflows/pr_title.yml @@ -22,6 +22,7 @@ jobs: # with in .github/dependabot.yml. scopes: | ai + ondevice repo docs deps @@ -33,13 +34,14 @@ jobs: needs: conventional_pr_title # Trigger after the [conventional_pr_title] completes runs-on: ubuntu-latest steps: - # Only `ai` is mapped, so `repo`/`docs`/`deps` PRs aren't required to - # touch a CHANGELOG. + # Only the package scopes are mapped, so `repo`/`docs`/`deps` PRs aren't + # required to touch a CHANGELOG. - uses: GetStream/verify-semantic-changelog-update@main with: scopes: | { - "ai": "packages/stream_chat_flutter_ai" + "ai": "packages/stream_chat_flutter_ai", + "ondevice": "packages/stream_chat_flutter_ai_ondevice" } env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_publish.yml b/.github/workflows/release_publish.yml index 790ef21..4bb7c84 100644 --- a/.github/workflows/release_publish.yml +++ b/.github/workflows/release_publish.yml @@ -54,20 +54,21 @@ jobs: run: | set -euo pipefail - version=$(sed -n 's/^version: *//p' packages/stream_chat_flutter_ai/pubspec.yaml) - if [[ "$TAG" != "v$version" ]]; then - echo "::error ::❌ Tag $TAG does not match pubspec version $version." - exit 1 - fi + .github/workflows/scripts/verify-release-versions.sh "$TAG" - echo "✅ $TAG matches the pubspec." + echo "✅ $TAG matches every publishable package." # Not `melos run lint:pub`: that skips versions already on pub.dev, so a - # manual run before the version bump would validate nothing. + # manual run before the version bump would validate nothing. In the pub + # workspace a dependent resolves its sibling locally, so this passes + # before the sibling's new version is on pub.dev. - name: 🌵 Dry Run - working-directory: packages/stream_chat_flutter_ai - run: flutter pub publish --dry-run + run: melos exec --no-private --order-dependents --concurrency 1 --fail-fast -- flutter pub publish --dry-run + # Publishes dependencies first (`orderDependents`), so pub.dev already + # has stream_chat_flutter_ai X.Y.Z when the packages that depend on it + # upload. `release:pub` retries each upload once if pub.dev hasn't caught + # up. A re-run of this workflow skips packages already published. - name: 📢 Release to pub.dev if: github.event_name == 'push' run: melos run release:pub diff --git a/.github/workflows/release_tag.yml b/.github/workflows/release_tag.yml index 03808c1..1db0b2b 100644 --- a/.github/workflows/release_tag.yml +++ b/.github/workflows/release_tag.yml @@ -60,20 +60,16 @@ jobs: run: | set -euo pipefail - # pub.dev rejects a tag that doesn't match the pubspec version, but - # only after the tag is pushed — catch the mismatch before that. - version=$(sed -n 's/^version: *//p' packages/stream_chat_flutter_ai/pubspec.yaml) - if [[ "$TAG" != "v$version" ]]; then - echo "::error ::❌ Tag $TAG does not match pubspec version $version." - exit 1 - fi + # Every publishable package is released in lockstep, so each + # pubspec must carry the tag's version. + .github/workflows/scripts/verify-release-versions.sh "$TAG" if git ls-remote --exit-code --tags origin "refs/tags/$TAG" > /dev/null; then echo "::error ::❌ Tag $TAG already exists." exit 1 fi - echo "✅ $TAG matches the pubspec and is not tagged yet." + echo "✅ $TAG matches every publishable package and is not tagged yet." - name: 🚀 Create and Push Tag shell: bash diff --git a/.github/workflows/scripts/verify-release-versions.sh b/.github/workflows/scripts/verify-release-versions.sh new file mode 100755 index 0000000..7014878 --- /dev/null +++ b/.github/workflows/scripts/verify-release-versions.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Checks that a release tag matches every publishable package. +# +# Usage: verify-release-versions.sh vX.Y.Z +# +# The publishable packages are released in lockstep, so each one's `version:` +# must equal the tag, and a dependency on another publishable package must be +# `^X.Y.Z`. pub.dev rejects a tag that doesn't match the pubspec, but only +# after the tag is pushed — this catches the mismatch before that. +# +# A package is publishable when its pubspec sits at `packages//pubspec.yaml` +# and has no `publish_to: none`; examples live one level deeper, so the glob +# never reaches them. Run from the repository root. +set -euo pipefail + +tag="${1:?usage: $0 vX.Y.Z}" +version="${tag#v}" + +names=() +pubspecs=() +for pubspec in packages/*/pubspec.yaml; do + if grep -qE "^publish_to: *[\"']?none" "$pubspec"; then + continue + fi + names+=("$(sed -n 's/^name: *//p' "$pubspec")") + pubspecs+=("$pubspec") +done + +if [[ ${#pubspecs[@]} -eq 0 ]]; then + echo "::error ::❌ No publishable package found under packages/." + exit 1 +fi + +failed=0 +for i in "${!pubspecs[@]}"; do + pubspec="${pubspecs[$i]}" + name="${names[$i]}" + + package_version=$(sed -n 's/^version: *//p' "$pubspec") + if [[ "$package_version" != "$version" ]]; then + echo "::error file=$pubspec::❌ Tag $tag does not match $name's pubspec version $package_version." + failed=1 + else + echo "✅ $name $package_version" + fi + + for dep in "${names[@]}"; do + # The constraint under `dependencies:` only; dev_dependencies aren't published. + constraint=$(awk -v dep="$dep:" ' + /^[^ #]/ { in_deps = ($1 == "dependencies:") } + in_deps && $1 == dep { print $2 } + ' "$pubspec") + if [[ -n "$constraint" && "$constraint" != "^$version" ]]; then + echo "::error file=$pubspec::❌ $name depends on $dep $constraint; a lockstep release needs ^$version." + failed=1 + fi + done +done + +exit "$failed" diff --git a/CLAUDE.md b/CLAUDE.md index 62c35f4..7225da1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,12 +24,16 @@ This is a [Melos 8](https://melos.invertase.dev) monorepo built on [Dart pub workspaces](https://dart.dev/tools/pub/workspaces). ``` -pubspec.yaml # workspace root: `workspace:` member list + `melos:` config -analysis_options.yaml # lint + formatter config for every member -packages/stream_chat_flutter_ai/ # the published package -packages/stream_chat_flutter_ai/example # showcase app, publish_to: none +pubspec.yaml # workspace root: `workspace:` member list + `melos:` config +analysis_options.yaml # lint + formatter config for every member +packages/stream_chat_flutter_ai/ # the core published package +packages/stream_chat_flutter_ai/example # showcase app, publish_to: none +packages/stream_chat_flutter_ai_ondevice/ # published plugin: on-device model (Gemini Nano / Apple Foundation Models) ``` +The two published packages are versioned in lockstep — see [Releasing](#releasing). The CI and release tooling +treat every `packages/*/` whose pubspec isn't `publish_to: none` as publishable. + Consequences worth remembering: - There is **no `melos.yaml`** — Melos 7 removed it in favour of the `melos:` key in the root @@ -91,21 +95,28 @@ sharing `.github/actions/package_analysis`), `pr_title` (conventional-commit enf ## Releasing -Releases follow the same flow as `stream-chat-flutter` and `stream-feeds-flutter`: +Releases follow the same flow as `stream-chat-flutter` and `stream-feeds-flutter`. The published packages +(`stream_chat_flutter_ai` and `stream_chat_flutter_ai_ondevice`) are released **in lockstep**: they always share +one `version:`, one `v` tag publishes both, and the ondevice package depends on +`stream_chat_flutter_ai: ^` (set in the root `melos.command.bootstrap.dependencies` list). -1. `/release-pr ` (`.claude/skills/release-pr/SKILL.md`) bumps the version, promotes - `## Upcoming` to `## ` in the CHANGELOG and opens a `chore(repo): release v` PR. +1. `/release-pr ` (`.claude/skills/release-pr/SKILL.md`) bumps both versions and the lockstep + constraint, promotes `## Upcoming` to `## ` in both CHANGELOGs and opens a + `chore(repo): release v` PR. 2. The PR is **squash-merged**, so that title becomes the tip commit on `main`. -3. `release_tag.yml` reads `v` from that commit, checks it matches the pubspec and isn't - tagged yet, and pushes the tag with `BOT_GITHUB_API_TOKEN` (a `GITHUB_TOKEN` push wouldn't trigger - the next workflow). -4. `release_publish.yml` runs on the tag, publishes over pub.dev OIDC and creates the GitHub Release. +3. `release_tag.yml` reads `v` from that commit, checks it matches every publishable pubspec + (`.github/workflows/scripts/verify-release-versions.sh`) and isn't tagged yet, and pushes the tag with + `BOT_GITHUB_API_TOKEN` (a `GITHUB_TOKEN` push wouldn't trigger the next workflow). +4. `release_publish.yml` runs on the tag, publishes over pub.dev OIDC — core first, since the ondevice + upload needs core `` on pub.dev — and creates the GitHub Release. Nobody's pub.dev account publishes — pub.dev trusts this repo's tag pushes instead. That needs one-time -setup outside git: automated publishing enabled on the package's pub.dev admin page (repository +setup outside git: automated publishing enabled on **each** package's pub.dev admin page (repository `GetStream/stream-chat-flutter-ai`, tag pattern `v{{version}}`, "Require GitHub Actions environment" left empty) and the `BOT_GITHUB_API_TOKEN` repo secret. Any `v*` tag push publishes from that ref's workflow, so a tag ruleset limiting -`refs/tags/v*` to the bot is worth adding too. +`refs/tags/v*` to the bot is worth adding too. pub.dev can't create a package through automated publishing, +so a new package's first version is uploaded by hand by a publisher member before its admin page offers the +setting; a release tag after that skips it if that version is already live. Running `release_publish` manually (`workflow_dispatch`) is a dry run: it validates the package but skips the publish and the GitHub Release. Below `1.0.0` a breaking release bumps the minor version @@ -128,7 +139,8 @@ workflows can't tag or publish a `+build` version. ## PR & commit conventions PR titles are enforced as `(): ` with a required scope from: -`ai`, `repo`, `docs`, `deps`. A PR scoped `ai` must also update -`packages/stream_chat_flutter_ai/CHANGELOG.md` — CI checks this. Changelog entries go under the +`ai`, `ondevice`, `repo`, `docs`, `deps`. A PR scoped `ai` must also update +`packages/stream_chat_flutter_ai/CHANGELOG.md`, and one scoped `ondevice` must update +`packages/stream_chat_flutter_ai_ondevice/CHANGELOG.md` — CI checks this. Changelog entries go under the existing emoji headings: `💥 Breaking` (first, one line per breaking change), `✅ Added`, `🔄 Changed`, `🐞 Fixed`, `🚀 Performance`. diff --git a/pubspec.yaml b/pubspec.yaml index a6fc99b..4b4620f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,9 +101,17 @@ melos: release:pub: exec: - command: flutter pub publish -f + # The packages are released in lockstep and a dependent requires its + # sibling's new version, so `orderDependents` publishes + # stream_chat_flutter_ai first. pub.dev can take a moment to serve a + # version it just accepted, so a failed upload is retried once after a + # minute. + command: >- + flutter pub publish -f || + { echo "Retrying $MELOS_PACKAGE_NAME in 60s"; sleep 60; flutter pub publish -f; } concurrency: 1 orderDependents: true + failFast: true description: | Publish all publishable packages to pub.dev. - Note: run by `release_publish.yml` on a release tag. Never run it locally.