Skip to content

ci: automate pub.dev publishing in dependency order#176

Merged
nank1ro merged 1 commit into
mainfrom
ci/automate-pubdev-release
Jun 30, 2026
Merged

ci: automate pub.dev publishing in dependency order#176
nank1ro merged 1 commit into
mainfrom
ci/automate-pubdev-release

Conversation

@nank1ro

@nank1ro nank1ro commented Jun 30, 2026

Copy link
Copy Markdown
Owner

What

One-click release orchestration. Adds a workflow_dispatch workflow that publishes every package whose pubspec version is not yet on pub.dev, in dependency order, waiting for each to become available before releasing its dependents — and skipping any version already published. This automates the manual "create release+tag → wait for pub.dev → repeat for the next package" loop.

Dependency order: solidart → flutter_solidart → solidart_hooks / solidart_lint.

Changes

  • scripts/release.sh (new) — for each package in order: read version:, skip if already on pub.dev, abort with guidance if the tag exists but isn't published, create a GitHub Release + tag <name>-v<version> (notes from the matching CHANGELOG section, --prerelease for -dev versions), then poll pub.dev (up to 30 min, cache-busted) until the version is live.
  • .github/workflows/release.yaml (new) — workflow_dispatch with a dry_run toggle. Uses secrets.RELEASE_PAT so the tags it creates trigger the existing publish_*.yaml workflows (the default GITHUB_TOKEN would not, due to GitHub's anti-recursion rule).
  • publish_*.yaml (4 files) — widen each tag glob to <pkg>-v[0-9]+.[0-9]+.[0-9]+* so current -dev.N prerelease tags (and +N build tags) actually trigger publishing. The solidart DevTools-extension build step is untouched.

Required setup

  • Repo secret RELEASE_PAT: fine-grained PAT scoped to this repo with Contents: write (already added).

How it was verified

  • bash -n clean; full DRY_RUN run listed all 4 packages in order with correct tags.
  • is_published correctly distinguished published (solidart 2.8.6) from unpublished (3.0.0-dev.1, 999.0.0).
  • Version parsing, tag computation, prerelease detection, and CHANGELOG extraction validated against every package.
  • All five workflow YAMLs parse.

How to use after merge

Actions → "Release to pub.dev (orchestrated)"Run workflow. Start with dry_run: true to preview, then run with false.

Note: the workflow only appears in the Actions UI once this is on the default branch (i.e. after merge).

Summary by CodeRabbit

  • New Features

    • Added a one-click release flow for publishing package updates.
    • Added a dry-run option to preview releases without creating tags or publishing anything.
    • Expanded release tag detection so more version suffixes are recognized automatically.
  • Bug Fixes

    • Improved release triggering for versioned tags, reducing missed publishes for build and prerelease versions.

Add a one-click `release.yaml` (workflow_dispatch) that releases every
package whose pubspec version is not yet on pub.dev, in dependency order
(solidart -> flutter_solidart -> solidart_hooks/solidart_lint), waiting
for each to become available before tagging its dependents, and skipping
any version already published.

- scripts/release.sh: read pubspec version, skip if already on pub.dev,
  guard against existing-but-unpublished tags, create GitHub release+tag
  (notes from CHANGELOG), then poll pub.dev until the version is live.
- release.yaml: workflow_dispatch with a dry_run toggle; uses RELEASE_PAT
  so the pushed tags trigger the existing publish_*.yaml workflows
  (the default GITHUB_TOKEN would not).
- publish_*.yaml: widen tag globs to also match prerelease (-dev.N) and
  build (+N) tags, e.g. `<pkg>-v[0-9]+.[0-9]+.[0-9]+*`, so current
  -dev versions actually trigger publishing.

The solidart DevTools-extension build step is untouched; the orchestrator
only triggers the existing per-package workflows via tags.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a new release.yaml workflow and scripts/release.sh script for manually triggered, dependency-ordered pub.dev publishing via GitHub Releases and tags. Also consolidates the tag-trigger regex patterns in the four existing publish_*.yaml workflows from two explicit patterns to a single wildcard pattern each.

Changes

Release orchestration and tag trigger consolidation

Layer / File(s) Summary
Consolidated tag trigger patterns
.github/workflows/publish_flutter_solidart.yaml, .github/workflows/publish_solidart.yaml, .github/workflows/publish_solidart_hooks.yaml, .github/workflows/publish_solidart_lint.yaml
Each workflow replaces two explicit semver tag regexes with a single <package>-v[0-9]+.[0-9]+.[0-9]+* pattern covering plain versions and additional suffixes.
release.yaml orchestration workflow
.github/workflows/release.yaml
New workflow_dispatch workflow with a dry_run boolean input, contents: write permissions, full-history checkout, and execution of scripts/release.sh with RELEASE_PAT as GH_TOKEN.
release.sh helper functions and package loop
scripts/release.sh
Script declares PACKAGES in dependency order, implements read_version, is_published (pub.dev API with cache-busting), tag_exists, changelog_notes, and wait_for_publish. The main loop skips published versions, errors on stale tags, creates GitHub releases with changelog notes (marking prereleases for - versions), and polls pub.dev before continuing to dependents.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • nank1ro/solidart#137: Added the solidart_hooks publish workflow with the original tag patterns that this PR consolidates into a single wildcard regex.

Poem

🐇 Hippity-hop, one pattern to rule them all,
A script that checks pub.dev before the curtain call,
It reads the changelog, creates a release with care,
Then polls and waits to see the version there.
Done! says the rabbit, tags neatly in a row. 🏷️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: automating pub.dev publishing in dependency order.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/automate-pubdev-release

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.

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (4b07edb) to head (aa544b9).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main      #176   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           18        18           
  Lines          892       892           
=========================================
  Hits           892       892           
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nank1ro nank1ro merged commit 17d9b48 into main Jun 30, 2026
3 of 4 checks passed
@nank1ro nank1ro deleted the ci/automate-pubdev-release branch June 30, 2026 13:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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.yaml (2)

26-28: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Drop persisted checkout credentials.

This job only needs repository contents after checkout, but actions/checkout will still leave a write-capable token in the local git config. Setting persist-credentials: false removes an unnecessary credential from the workspace.

Suggested change
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0 # full history + tags
+          persist-credentials: false
🤖 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.yaml around lines 26 - 28, The release workflow
checkout step is leaving persisted git credentials in the workspace, which is
unnecessary for this job. Update the existing actions/checkout@v4 step in the
release workflow to disable credential persistence by setting
persist-credentials to false alongside fetch-depth, so the job only keeps
repository contents and not a write-capable token.

Source: Linters/SAST tools


22-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Serialize manual release runs.

This workflow performs non-idempotent external writes, so two workflow_dispatch runs can race past the early checks and leave one run failing mid-release. A workflow-level concurrency group would make retries deterministic.

[details omitted in scratchpad]

Suggested change
 permissions:
   contents: write
 
+concurrency:
+  group: release-pubdev
+  cancel-in-progress: false
+
 jobs:
🤖 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.yaml around lines 22 - 24, Add a workflow-level
concurrency guard to the release workflow so only one manual release run can
execute at a time. Update the release job/workflow in release.yaml by
introducing a concurrency group tied to the workflow or ref, and set it to
cancel or queue overlapping workflow_dispatch runs; use the existing release job
definition as the anchor for where to apply it.
🤖 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/publish_flutter_solidart.yaml:
- Line 6: The tag filter in the publish workflow is using regex-like syntax
instead of a GitHub Actions glob, so it will not match the release tags emitted
by scripts/release.sh. Update the tags pattern in this workflow to a valid glob
that matches the <name>-v<version> tag shape, and apply the same change to the
sibling publish workflows that duplicate this filter. If semver validation is
still required, enforce it inside the job rather than in the trigger pattern.

In `@scripts/release.sh`:
- Around line 48-55: The is_published helper currently swallows curl/HTTP
failures and returns “not published,” which can let release tagging proceed
during pub.dev outages. Update is_published to return a distinct
non-published/non-error status for successful lookups only, and make the caller
path that reaches gh release create abort on lookup failures while keeping the
polling logic retryable. Use is_published, curl, and the gh release create flow
in scripts/release.sh to locate the change.

---

Nitpick comments:
In @.github/workflows/release.yaml:
- Around line 26-28: The release workflow checkout step is leaving persisted git
credentials in the workspace, which is unnecessary for this job. Update the
existing actions/checkout@v4 step in the release workflow to disable credential
persistence by setting persist-credentials to false alongside fetch-depth, so
the job only keeps repository contents and not a write-capable token.
- Around line 22-24: Add a workflow-level concurrency guard to the release
workflow so only one manual release run can execute at a time. Update the
release job/workflow in release.yaml by introducing a concurrency group tied to
the workflow or ref, and set it to cancel or queue overlapping workflow_dispatch
runs; use the existing release job definition as the anchor for where to apply
it.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8114e959-bdbb-482c-9bc4-05c9a7c87a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 4b07edb and aa544b9.

📒 Files selected for processing (6)
  • .github/workflows/publish_flutter_solidart.yaml
  • .github/workflows/publish_solidart.yaml
  • .github/workflows/publish_solidart_hooks.yaml
  • .github/workflows/publish_solidart_lint.yaml
  • .github/workflows/release.yaml
  • scripts/release.sh

tags:
- 'flutter_solidart-v[0-9]+.[0-9]+.[0-9]+' # Matches flutter_solidart-v1.2.3
- 'flutter_solidart-v[0-9]+.[0-9]+.[0-9]+\+[0-9]+' # Matches flutter_solidart-v1.2.3+1
- "flutter_solidart-v[0-9]+.[0-9]+.[0-9]+*" # Matches flutter_solidart-v1.2.3, -v1.2.3+1 (build), -v1.2.3-dev.1 (prerelease)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files .github/workflows scripts | sed -n '1,200p'

Repository: nank1ro/solidart

Length of output: 439


🏁 Script executed:

sed -n '1,220p' .github/workflows/publish_flutter_solidart.yaml
printf '\n---\n'
sed -n '1,220p' .github/workflows/publish_solidart.yaml
printf '\n---\n'
sed -n '1,220p' .github/workflows/publish_solidart_hooks.yaml
printf '\n---\n'
sed -n '1,220p' .github/workflows/publish_solidart_lint.yaml
printf '\n---\n'
sed -n '1,220p' scripts/release.sh

Repository: nank1ro/solidart

Length of output: 7528


🌐 Web query:

GitHub Actions workflow syntax push tags glob pattern square brackets plus literal semver tag matching

💡 Result:

GitHub Actions workflow on.push.tags filters support glob patterns, not regular expressions [1]. While glob syntax includes some special characters, it does not support full regex character classes or quantifier-based pattern matching (such as [0-9]+) [1]. Key technical details regarding your query include: 1. Glob vs. Regex: The tags filter uses glob patterns (e.g., *, ``, ?, `!`) [1][4][8]. Attempts to use regex-style syntax like `[0-9]+` will not function as regex; GitHub Actions will interpret these literally or fail to match them against the tag string [1]. 2. Square Brackets in YAML: Square brackets `[` and `]` are special characters in YAML [2]. If your tag pattern starts with `[` or contains them in a way that YAML interprets as a flow sequence, you must enclose the entire pattern in quotes (e.g., `'[pattern]'`) to ensure it is treated as a string literal [2][3]. 3. Literal Matching: If a tag name happens to contain glob characters (like ``, `?`, or `+`) and you want to match that tag literally, you must escape those characters with a backslash `` [4][5][6]. 4. SemVer Matching: Because GitHub Actions does not support regex, there is no native way to perform complex SemVer range matching (e.g., "all tags between 1.0.0 and 2.0.0") directly in the workflow trigger. Common strategies include using broad glob patterns like `v1.` or `v*` and then using a conditional step within the workflow (e.g., `if: ${{ startsWith(github.ref_name, 'v1.') }}`) or a dedicated action to validate the version [7][1]. In summary, you cannot use regex for tag filtering. You should rely on glob patterns for broad inclusion/exclusion and handle specific version logic inside the workflow job itself.

Citations:


Fix the tag filter syntax GitHub Actions treats tags: as globs, not regex, so this pattern does not match the <name>-v<version> tags emitted by scripts/release.sh. Use a valid glob and validate the version in the job if semver constraints are needed. The same pattern is duplicated in the sibling publish workflows.

🤖 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/publish_flutter_solidart.yaml at line 6, The tag filter in
the publish workflow is using regex-like syntax instead of a GitHub Actions
glob, so it will not match the release tags emitted by scripts/release.sh.
Update the tags pattern in this workflow to a valid glob that matches the
<name>-v<version> tag shape, and apply the same change to the sibling publish
workflows that duplicate this filter. If semver validation is still required,
enforce it inside the job rather than in the trigger pattern.

Comment thread scripts/release.sh
Comment on lines +48 to +55
# True when <version> ($2) is already published for package <name> ($1).
# A cache-busting query + no-cache header avoid a stale CDN response masking a
# just-published version. Network/HTTP errors are treated as "not published".
is_published() {
local body
body="$(curl -fsS -H 'Cache-Control: no-cache' "${PUB_API}/$1?_=${RANDOM}" 2>/dev/null || true)"
[ -n "$body" ] || return 1
printf '%s' "$body" | jq -e --arg v "$2" '.versions[]?.version | select(. == $v)' >/dev/null 2>&1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fail closed when the pub.dev lookup errors.

Treating curl/HTTP failures as “not published” breaks the workflow’s main safety guarantee. A transient pub.dev outage can make this script create a new release/tag for a version that is already live, which then triggers a downstream publish that can only fail. Return a distinct status for lookup failures and abort before gh release create; only the polling path should treat lookup errors as retryable.

🤖 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 `@scripts/release.sh` around lines 48 - 55, The is_published helper currently
swallows curl/HTTP failures and returns “not published,” which can let release
tagging proceed during pub.dev outages. Update is_published to return a distinct
non-published/non-error status for successful lookups only, and make the caller
path that reaches gh release create abort on lookup failures while keeping the
polling logic retryable. Use is_published, curl, and the gh release create flow
in scripts/release.sh to locate the change.

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