Skip to content

ci(release): publish to pub.dev via OIDC trusted publishing#20

Open
hakimjonas wants to merge 2 commits into
mainfrom
ci/automate-pub-dev-publish
Open

ci(release): publish to pub.dev via OIDC trusted publishing#20
hakimjonas wants to merge 2 commits into
mainfrom
ci/automate-pub-dev-publish

Conversation

@hakimjonas

@hakimjonas hakimjonas commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Item 1b of the 0.14.0 cycle. Hardened after an adversarial review (see below).

Why

release.yml had no dart pub publish step — pub.dev publishing was manual, which is why 0.13.0 shipped to GitHub/brew/scoop/MCP but pub.dev stayed at 0.12.0 until caught. Automate it so it can't slip again.

What

Adds a publish-pub-dev job that calls Dart's official reusable workflow dart-lang/setup-dart/.github/workflows/publish.yml@v1, gated on non-prerelease tags, needs: release.

publish-pub-dev:
  needs: release
  if: ${{ !contains(github.ref_name, '-') }}
  permissions:
    id-token: write
  uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1

Adversarial review (what changed from the first cut)

The first version hand-rolled setup-dart + dart pub publish --force. Review against the pub.dev automated-publishing docs and the canonical reusable workflow found:

  • Auth was correct (setup-dart provisions the OIDC token; id-token: write is the right, sufficient permission — verified the caller must grant it and a reusable workflow can't escalate).
  • But it reinvented the official path, which the docs strongly encourage. The reusable workflow additionally runs dart pub publish --dry-run before the irreversible -f — important because pub.dev versions are immutable.
  • Dropped redundant gen_version/gen_skill steps — those files are tracked and kept current by the generated-files-in-sync CI check; the committed tree is what publishes.
  • Fixed a misleading comment ("same mechanism as publish-mcp" — MCP registry OIDC ≠ pub.dev OIDC integration).

Verified: @v1 resolves (tag 65eb853, the same ref the docs use); job-level uses/if/needs/permissions is the legal reusable-workflow shape; no with: or secrets: inherit needed.

One-time setup required before this works

pub.dev → lambe → Admin → Automated publishing → Enable, repo hakimjonas/lambe, tag pattern v{{version}}. pubspec version must match the tag. Until enabled, the job fails in isolation — the rest of the release is unaffected (needs: release is ordering only).

Publish gate

No required-reviewer environment: added — solo maintainer, tag trigger + branch protection suffice. The reusable workflow accepts an environment: input, so this is a one-line add later if collaborators join.

Workflow-only change — no lib/CLI code touched.

pub.dev was published BY HAND — release.yml had no publish step — so
0.13.0 shipped to GitHub/brew/scoop/MCP while pub.dev stayed at 0.12.0
until caught days later. Automate it.

- New publish-pub-dev job: dart pub publish --force, authenticated by
  OIDC (id-token: write), mirroring the publish-mcp pattern. No stored
  credentials.
- Gated on !contains(ref_name, '-') like the other publish jobs, and
  needs: release for ordering.
- Regenerates _version.dart / _skill.dart before publishing as a
  safety belt (they're tracked + kept in sync by CI, but regenerating
  guarantees the published constant matches the tag regardless).

One-time setup required on pub.dev (Admin → Automated publishing) before
this works; until enabled the job fails in isolation without affecting
the rest of the release. Documented inline.
Adversarial review of the first cut (hand-rolled setup-dart + `dart pub
publish --force`) found it correct on auth but reinventing what Dart's
official reusable workflow does — which the docs strongly encourage and
which already includes a dry-run before the irreversible publish.

Replace the custom job with a call to
`dart-lang/setup-dart/.github/workflows/publish.yml@v1`:

- The reusable workflow runs `dart pub publish --dry-run` before
  `-f`, so validation/resolution failures surface before the
  irrevocable step (pub.dev versions are immutable).
- setup-dart provisions the OIDC token; caller passes `id-token: write`
  (verified: caller must grant it, reusable workflow can't escalate).
- Dropped the redundant gen_version/gen_skill steps — those files are
  tracked and kept current by the generated-files CI check; the
  committed tree is what publishes.
- Fixed the misleading "same mechanism as publish-mcp" comment (MCP
  registry OIDC is a different integration than pub.dev's).

Verified: @v1 resolves (tag 65eb853, same ref the official docs use);
job-level uses/if/needs/permissions is the legal reusable-workflow
shape; no `with:` or `secrets: inherit` needed (inputs optional, OIDC
not secrets). One-time pub.dev publisher setup still required before it
runs; fails in isolation until then.
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