ci: adopt release-please for monorepo releases - #1051
Conversation
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
|
🚀 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. |
# Conflicts: # pnpm-lock.yaml
…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.
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).
# Conflicts: # package.json # pnpm-lock.yaml
karinamzalez
left a comment
There was a problem hiding this comment.
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 READMEPR 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?
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.
# Conflicts: # lib/python-sdk/package.json
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.
|
@karinamzalez thank you for the review! On docs, releases are scoped by path, not by the title's scope, so Good catch on |
Summary
Changes proposed
Replaces the Changesets release workflow with release-please in manifest mode, so the conventional-commit history on
maindrives versioning and releases for the four published packages.Why: a forgotten
.changeset/*.mdships 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 tomainby CI, which is why bothmainprotection rulesets are off today; and Changesets is Node-only, so the Python SDK carries a stubpackage.jsonplus 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:
release-please-config.json+.release-please-manifest.json(seeded with the current versions) and arelease-please.ymlworkflow that maintains one Release PR per package and fans out to the publish workflows when a Release PR merges. The existingname@versiontag format is preserved exactly.cd-deploy-lib-*.ymlpublish workflows: the release-creation job is gone (release-please owns tag + GitHub release + notes) and the deploy job is nowworkflow_callfromrelease-please.yml, keepingworkflow_dispatchas a manual fallback for re-running a publish against an existing tag. A prefix guard onrelease_tagprotects 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'sworkflow_call.secrets, rather than inheriting every repo secret; the deploy jobs also drop an unusedid-token: writepermission..changeset/,ci-bump-version.yml(including its direct-to-mainbump commit that required branch protections off), the release-notes script +.github/release-config/, the vestigiallib/python-sdk/package.json, the root@changesets/*devDependencies, and thetomlq/tomlkitdev-deps that only served the deleted bump script. The Python version is now edited only inpyproject.toml(release-type: pythonupdates it natively).common_grants_sdk/__init__.py, which was stuck at__version__ = "0.1.0"(package is at 0.8.0);release-type: pythonupdates it natively on each release (it probes both the hyphen and underscore forms of thepyproject.tomlname, socommon_grants_sdk/__init__.pyis found without anextra-filesentry or an annotation comment).lib/python-sdk/tests/test_version.pynow asserts__version__equals the[tool.poetry]version, so the drift that hid this for several releases fails the Python gate instead of shipping.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 nochangelog-sectionsentry 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.lib/README.md, the per-packageDEVELOPMENT.mdrunbooks,DEPENDENCY_MANAGEMENT.md, andCONTRIBUTING.mddescribe 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/testare hidden and don't trigger releases; every other accepted type (fix,perf,revert,docs,refactor,build) cuts a patch,feata 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 withGITHUB_TOKENdon't triggeron: releaseworkflows.Verification:
release-please release-pr --dry-runagainst this branch: config parses, all four packages locate their last release from the existing tags, and it would open Version PRs for@common-grants/core0.3.6,@common-grants/cli0.3.8, andcommon-grants-sdk0.8.1 (the pendingfix(deps)advisory work since those tags;lib/ts-sdkcorrectly has nothing releasable since 0.6.1).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):
PR_TITLEand messagePR_BODY. The previousCOMMIT_OR_PR_TITLElet 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.mainbranch-protection rulesets (acceptance criterion on [Release] Adopt release-please for monorepo releases #870). This PR removesci-bump-version.yml, which was the reason they were turned off, and release-please only opens PRs rather than pushing. One direct-to-mainpush 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.@common-grants/*@*andcommon-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 — onmainit readschore(main): ...):