Skip to content

ci: adopt release-please for monorepo releases - #1051

Open
SnowboardTechie wants to merge 20 commits into
mainfrom
870-adopt-release-please
Open

ci: adopt release-please for monorepo releases#1051
SnowboardTechie wants to merge 20 commits into
mainfrom
870-adopt-release-please

Conversation

@SnowboardTechie

@SnowboardTechie SnowboardTechie commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Changes proposed

Replaces the Changesets release workflow with release-please in manifest mode, so the conventional-commit history on main drives versioning and releases for the four published packages.

Why: a forgotten .changeset/*.md ships a change unversioned with nothing to catch it; versions bump the moment any changeset lands, so merges can't be batched into one release; the bump commit is pushed straight to main by CI, which is why both main protection rulesets are off today; and Changesets is Node-only, so the Python SDK carries a stub package.json plus a shell script that greps changeset files to drive the Poetry bump. The options weighed, including keeping Changesets with its official Version-PR action, are recorded in the new ADR.

What changed:

  • Added release-please-config.json + .release-please-manifest.json (seeded with the current versions) and a release-please.yml workflow that maintains one Release PR per package and fans out to the publish workflows when a Release PR merges. The existing name@version tag format is preserved exactly.
  • Rewired the four cd-deploy-lib-*.yml publish workflows: the release-creation job is gone (release-please owns tag + GitHub release + notes) and the deploy job is now workflow_call from release-please.yml, keeping workflow_dispatch as a manual fallback for re-running a publish against an existing tag. A prefix guard on release_tag protects the manual path against a mistyped ref (replacing the validation the deleted release-notes script used to do). Each publish call receives only the one registry token it uses, declared in the called workflow's workflow_call.secrets, rather than inheriting every repo secret; the deploy jobs also drop an unused id-token: write permission.
  • Removed the Changesets machinery: .changeset/, ci-bump-version.yml (including its direct-to-main bump commit that required branch protections off), the release-notes script + .github/release-config/, the vestigial lib/python-sdk/package.json, the root @changesets/* devDependencies, and the tomlq/tomlkit dev-deps that only served the deleted bump script. The Python version is now edited only in pyproject.toml (release-type: python updates it natively).
  • Fixed common_grants_sdk/__init__.py, which was stuck at __version__ = "0.1.0" (package is at 0.8.0); release-type: python updates it natively on each release (it probes both the hyphen and underscore forms of the pyproject.toml name, so common_grants_sdk/__init__.py is found without an extra-files entry or an annotation comment). lib/python-sdk/tests/test_version.py now asserts __version__ equals the [tool.poetry] version, so the drift that hid this for several releases fails the Python gate instead of shipping.
  • Added ci-pr-title.yml, which validates conventional-commit PR titles against exactly the types release-please treats as releasable, since squashed PR titles are now release-facing. It is a regex over the event payload rather than a third-party action, so the job needs no token scopes. A title whose type has no changelog-sections entry would otherwise merge and then ship unversioned with no error — release-please drops such a commit from the notes and skips the release when the notes come out empty.
  • Docs: new ADR 0027 records the decision (ADR 0010 gets a superseded banner); lib/README.md, the per-package DEVELOPMENT.md runbooks, DEPENDENCY_MANAGEMENT.md, and CONTRIBUTING.md describe the new flow.

Context for reviewers

How the new flow works: merge a PR titled fix(...)/feat(...) → release-please opens or updates that package's Release PR → merging the Release PR tags, creates the GitHub release, and publishes to npm/PyPI automatically. chore/ci/test are hidden and don't trigger releases; every other accepted type (fix, perf, revert, docs, refactor, build) cuts a patch, feat a minor, and a ! marker a breaking release (minor while pre-1.0). Publish fan-out happens via workflow outputs in the same run because tags created with GITHUB_TOKEN don't trigger on: release workflows.

Verification:

  • release-please release-pr --dry-run against this branch: config parses, all four packages locate their last release from the existing tags, and it would open Version PRs for @common-grants/core 0.3.6, @common-grants/cli 0.3.8, and common-grants-sdk 0.8.1 (the pending fix(deps) advisory work since those tags; lib/ts-sdk correctly has nothing releasable since 0.6.1).
  • Full workspace gate (pnpm run ci: core, changelog-emitter, cli, sdk, website) and the Python SDK gate (make checks && make test && make build, 397 tests, wheel builds at 0.8.0) pass locally.

Repo settings (admin):

  1. Already applied. Squash-merge settings now use commit title PR_TITLE and message PR_BODY. The previous COMMIT_OR_PR_TITLE let a single-commit PR take its subject from the commit rather than the PR title, which would have bypassed the title check this PR adds.
  2. After merge: re-enable the two main branch-protection rulesets (acceptance criterion on [Release] Adopt release-please for monorepo releases #870). This PR removes ci-bump-version.yml, which was the reason they were turned off, and release-please only opens PRs rather than pushing. One direct-to-main push does remain: deps-mapping-repo.yml's scheduled README-diagram commit, which is guarded by a no-op check and has not actually fired since December 2025. It will be blocked once the rulesets are on ("Protect main (pushes)" has no bypass actors), so it is worth converting to a PR-based step separately. "Allow GitHub Actions to create and approve pull requests" is already on and needs to stay on.
  3. Optional hardening: a tag-protection ruleset covering @common-grants/*@* and common-grants-sdk@* would require elevated permission to create release-shaped tags (the manual publish fallback trusts tag contents).

Known gap: commits between each package's last tag and this merge that don't follow the conventional format (e.g. #918, #947) are invisible to release-please, so the first Version PRs' changelogs may under-report. Changelog edits pushed to a Release PR branch survive, so the first ones can be hand-amended if it matters.

Additional information

Dry-run output (abridged; run with --target-branch=870-adopt-release-please, so the PR-title scope shows the branch name — on main it reads chore(main): ...):

✔ Building strategies by path
❯ lib/core: node · lib/cli: node · lib/ts-sdk: node · lib/python-sdk: python
❯ Found release for path lib/ts-sdk, @common-grants/sdk@0.6.1
❯ Found release for path lib/python-sdk, common-grants-sdk@0.8.0
❯ Found release for path lib/core, @common-grants/core@0.3.5
❯ found: @common-grants/cli@0.3.7 1f67e30b8153922b67b23d4a0aac54762d6005ff
Would open 3 pull requests
title: chore(870-adopt-release-please): release @common-grants/core 0.3.6
title: chore(870-adopt-release-please): release @common-grants/cli 0.3.8
title: chore(870-adopt-release-please): release common-grants-sdk 0.8.1

Replaces the Changesets machinery with release-please in manifest mode
for the four published packages (issue #870):

- release-please-config.json / .release-please-manifest.json seeded with
  current versions; existing name@version tag format preserved
- release-please.yml maintains per-package Release PRs and fans out to
  the publish workflows via outputs (GITHUB_TOKEN-created releases do
  not trigger on:release workflows)
- cd-deploy-lib-*.yml lose the release-creation job (release-please owns
  tag + release + notes) and become workflow_call with a
  workflow_dispatch fallback for re-running a publish
- ci-bump-version.yml, .changeset/, the release-notes script and configs,
  and the vestigial lib/python-sdk/package.json are removed; the Python
  version now lives only in pyproject.toml (mirrored into __init__.py,
  which was stuck at 0.1.0)
- ci-pr-title.yml enforces conventional-commit PR titles, which drive
  version bumps once squash-merged
- tomlq/tomlkit dev-deps removed (only used by the deleted bump script)
- ADR 0027 records the release-please decision; ADR 0010 gets a
  superseded banner
- lib/README.md, per-package DEVELOPMENT.md runbooks,
  DEPENDENCY_MANAGEMENT.md, and CONTRIBUTING.md describe the
  conventional-commit + Release PR flow
@github-actions github-actions Bot added cli Issue or PR related to the @common-grants/cli library website Issues related to the website core Issues related to @common-grants/core library dependencies Pull requests that update a dependency file python Issue or PR related to Python tooling sdk Issue or PR related to our SDKs typescript Issue or PR related to TypeScript tooling py-sdk Related to Python SDK labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Website Preview Deployed!

Preview your changes at: https://cg-pr-1051.billy-daly.workers.dev

This preview will be automatically deleted when the PR is closed.

SnowboardTechie and others added 9 commits July 30, 2026 10:29
…ections

The title lint accepted commitizen's full type list while changelog-sections
listed only eight, so a `perf`/`revert`/`style` title passed CI and then shipped
unversioned: release-please drops a commit whose type has no section, and skips
the release entirely when the resulting notes are empty.

Add `perf` and `revert` as releasable sections and validate titles against
exactly that set, so an unreleasable title fails at PR time instead of silently
after merge. The check is a regex over the event payload rather than a
third-party action, so the job needs no token scopes at all.

Also correct the release tables in CONTRIBUTING.md and lib/README.md: `docs`,
`refactor`, and `build` are un-hidden in changelog-sections, so they cut a patch
release rather than being release-neutral as documented.
… findings

- Drop the redundant extra-files entry and x-release-please-version marker:
  release-please's python strategy natively updates common_grants_sdk/__init__.py
  (hyphen->underscore candidate paths in strategies/python.ts)
- Remove the unused id-token: write permission from the four deploy jobs; no
  step mints an OIDC token, and workflow_call would cap it to none anyway
- Restore a release_tag prefix guard on the deploy workflows, replacing the
  validation lost with the deleted release-notes script
- Document that GitHub's Revert button title fails the PR-title check and how
  to retitle, and that a breaking ! marker on chore/ci/test still releases
- Tighten the release-tag guard to an anchored X.Y.Z format check plus a
  refs/tags existence check, so a branch named like a tag can't be published
  via the manual workflow_dispatch fallback
- Route the Notify step's tag echo through env instead of inlining the
  workflow input into the script
- Note the breaking-! exception in DEPENDENCY_MANAGEMENT.md to match the
  other release docs, and correct ADR 0027 on where the Python version lives
actions/checkout resolves an unqualified ref to a branch when a branch and
tag share a name, so the show-ref guard could pass while a same-named branch
was actually checked out. Qualify the checkout ref as refs/tags/ instead —
the tag namespace is pinned at fetch time and a nonexistent tag fails the
checkout — and keep the guard as a format check only, now tolerant of
prerelease/build suffixes so a Release-As prerelease wouldn't fail the
automated publish path.
Move the format guard ahead of checkout (bare-workspace working-directory)
so no step ever touches content from an unvalidated ref, and allow the
combined semver prerelease+build suffix the widened pattern missed.
- Add lib/python-sdk/tests/test_version.py pinning __version__ to the
  pyproject.toml version. release-please's python strategy keeps the two in
  sync on release, but nothing caught the drift that left __version__ at
  0.1.0 while the package shipped through 0.8.0.
- Replace `secrets: inherit` on the four publish calls with the single secret
  each one consumes, declared in the callee's workflow_call. inherit passed
  every repo secret to workflows that reference exactly one.
- lib/changelog-emitter has no `"private": true` field; it is excluded from
  releases by omission from release-please-config.json, so say that instead.
@SnowboardTechie
SnowboardTechie marked this pull request as ready for review August 4, 2026 17:05
The workflow fires on every title or body edit and had no concurrency group,
so superseded runs kept queuing and holding a runner slot instead of being
cancelled. Ten runs accumulated on this branch, each checking a title that a
later run rechecked anyway.

Groups per PR number with cancel-in-progress, so only the newest title is
checked. This does not shorten GitHub's runner queue, which is what actually
delayed the runs (one waited 5m09s for a runner and then executed in under a
second) — it just stops spending a slot per edit.
Resolves a pnpm-lock.yaml conflict with #1054, which added a swagger-ui-dist
override and changed website deps while this branch removed the @changesets/*
devDependencies and the lib/python-sdk importer.

Resolved by taking main's lockfile and re-running pnpm install so this branch's
manifest deltas reapply on top, rather than hand-merging lockfile hunks.
Verified: no @changesets/* entries and no lib/python-sdk importer remain, main's
swagger-ui-dist override is present, and pnpm install --frozen-lockfile
reproduces the tree. pnpm-workspace.yaml auto-merged (main edited the overrides
block, this branch the packages list).

@karinamzalez karinamzalez 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.

Hi Bryan! It's really neat to see us adopting release-please! I have a few clarifying questions. Some are file-specific but the more generic one:

  • should docs only changes be hidden alongside chore/ci/test? -- e.g. a docs(core): fix typo in README PR would publish a new npm release .. I don't think we'd write a changeset for that?
  • is missing-release-check.yml still necessary? looks like it's a nightly job that flags tag without a GH release. I think under release-please, tags and releases are created together so it would never fire -- dead code?

Comment thread website/src/content/docs/governance/adr/0027-release-please.md Outdated
Comment thread .github/workflows/cd-deploy-lib-ts-sdk.yml
Comment thread CONTRIBUTING.md Outdated
Resolve pnpm-lock.yaml by taking main's resolutions (typescript 6, zod 4)
and regenerating with pnpm 11.20.0, which drops the lib/python-sdk
importer this branch removes from the workspace.
lib/cli 0.3.7 -> 0.3.8 and lib/ts-sdk 0.6.1 -> 0.7.0, matching what the
typescript v6 and zod v4 bumps released. Stale baselines here would make
release-please compute the next version from the wrong tag.
The nightly job flagged tags that had no GitHub release, a gap that existed
because ci-bump-version.yml pushed tags on merge while release creation was a
separate manual workflow_dispatch. release-please creates the tag and the
release in one step, so the state it watched for can no longer arise.
… retitle

Releases are scoped by file path, not by the title's scope, so docs(website)
releases nothing while docs inside a package does. Records what happens when a
PR spans two packages and when that warrants splitting it, and why the Revert
retitle matters: release-please cannot parse 'Revert "..."', so a revert merged
under that title ships with no version bump and no error.
Manifest moves ts-sdk to 0.7.1 and python-sdk to 0.8.1. The Python
dunder was left at 0.8.0 by the changesets bump, which pyproject.toml
already moved to 0.8.1; tests/test_version.py pins the two together.
@SnowboardTechie

Copy link
Copy Markdown
Collaborator Author

@karinamzalez thank you for the review! On docs, releases are scoped by path, not by the title's scope, so docs(website): fix typo releases nothing. Inside a package it does release, and I'd keep it: npm ships README.md in the tarball regardless of the files allowlist, so it's a real change to the published artifact. Nothing publishes on the merge either, it just opens or updates a Release PR that sits until we merge it. chore(core): is the lever for internal notes that shouldn't ship. Pushed that to CONTRIBUTING, it wasn't written down.

Good catch on missing-release-check.yml, that's dead and I've deleted it along with check-for-unreleased-tags.sh. It covered the old gap where ci-bump-version.yml pushed the tag on merge and release creation was a separate manual dispatch. release-please does both in one step.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Issue or PR related to the @common-grants/cli library core Issues related to @common-grants/core library dependencies Pull requests that update a dependency file py-sdk Related to Python SDK python Issue or PR related to Python tooling sdk Issue or PR related to our SDKs typescript Issue or PR related to TypeScript tooling website Issues related to the website

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Release] Adopt release-please for monorepo releases

2 participants