feat(release): migrate to release-please manifest-mode release automation - #168
Conversation
…tion Replace the per-package python-semantic-release + JS semantic-release workflows (release.yml, release-ts.yml, release-mcp.yml, update-mcp-dependency.yml) with release-please in manifest mode: - release-please.yml: the only workflow watching pushes to main. Opens or updates ONE aggregated release PR (separate-pull-requests: false) covering client, mcp, and ts; once that PR merges, creates all tags and draft GitHub Releases atomically at the merge commit. Never pushes to main itself. - release-pr-prepare.yml: runs only on the release PR branch. Resyncs uv.lock and the MCP server's frontapp-openapi-client>=X floor to the versions release-please just bumped, landing as a commit on the PR branch so it merges atomically with the version bump. - publish.yml: the only workflow that builds and publishes. Triggered exclusively by client-v*/mcp-v*/ts-v* tag pushes. Builds, publishes to PyPI/npm via OIDC, attaches assets to the still-draft release, then publishes it - never finalizing a release before its assets exist, so nothing is lost to GitHub's immutable-release asset rule. This removes every job that pushed directly to main during a release (the root cause of the races this design is meant to eliminate) and collapses the previously duplicated MCP release surfaces (release.yml's release-mcp job + release-mcp.yml) into one. Ref dougborg/katana-openapi-client#1002, closes #10.
…x client pin floor - Remove [tool.semantic_release] from pyproject.toml (root) and frontapp_mcp_server/pyproject.toml - versioning/tagging/changelogs are now release-please's job (see release-please-config.json). - Remove the python-semantic-release dev dependency and the frontapp-version/frontapp-release script entry points that backed it (semantic_release.cli:main). - Remove packages/frontapp-client/.releaserc.json and the @semantic-release/* + semantic-release devDependencies it required; regenerate pnpm-lock.yaml accordingly. - frontapp_mcp_server/pyproject.toml: set frontapp-openapi-client's floor to >=0.1.0 (the version seeded in .release-please-manifest.json and currently committed in pyproject.toml), replacing >=0.51.0, a version that has never been published. Fixes #165. release-please only ever bumps forward from the seeded version, so >=0.1.0 can never overshoot the client's actual first published version; the old >=0.51.0 floor was inherited from a sibling repo and never validated against what this repo would actually ship (dougborg/statuspro-openapi-client#63 was the same defect there). release-pr-prepare.yml keeps this floor truthful on every future release.
…-release guide - docs/RELEASE.md: rewritten for the release-please manifest-mode flow (release PR -> release-pr-prepare glue -> tag -> publish.yml), including the manual PyPI/npm Trusted Publisher prerequisites and the path-based vs scope-based bump-decision change. - Remove docs/MONOREPO_SEMANTIC_RELEASE.md; fold anything still relevant into docs/RELEASE.md. Update mkdocs.yml nav, docs/index.md toctree, docs/README.md's file listing, and the doc-tree diagram in docs/adr/0013-module-local-documentation.md accordingly. - frontapp_mcp_server/docs/deployment.md: replace the semantic-release-specific mechanics with a pointer to docs/RELEASE.md, keeping the MCP-specific verification/testing sections. - .github/workflows/README.md: describe the new release-please.yml / release-pr-prepare.yml / publish.yml topology in place of the old release.yml / release-ts.yml / update-mcp-dependency.yml. - README.md, docs/CONTRIBUTING.md: update commit-standards guidance - bump type still comes from the commit type, but which package(s) bump now depends on which paths a commit touches, not its scope.
There was a problem hiding this comment.
Semgrep OSS found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Matches the form already merged in stocktrim/statuspro (309159260+dougborg-release-please[bot]@users.noreply.github.com). Without the numeric id GitHub does not link the commit to the bot account, so release-PR commits would show an unlinked author. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRhJ5dF3N3z7X63aRaDBcQ
|
Reviewed. Two notes, one of which is a setup trap worth recording before anyone registers the publishers. Trusted-publisher registration must specify the environment name
None of those environments exist in this repo yet — Concretely, when registering at https://pypi.org/manage/account/publishing/ :
And the npm Trusted Publisher for Note this differs from how Both packages are new to PyPI, so these must be created as pending publishers (a trusted publisher cannot create a project that does not exist — that is the Bot commit identityPushed a small fix: What I checked and am satisfied with
Deliberately unresolvedThe first publish will still fail until the publishers above are registered. That is intended — the workflow is correct and inert rather than absent, and this repo has never published anything, so there is no regression risk in landing it before the registrations exist. |
mkdocs runs in --strict mode and aborts on links whose targets are not part of the docs tree. release-please-config.json and .release-please-manifest.json live at the repo root, so linking them failed the quality job: Aborted with 2 warnings in strict mode! Name them in code spans instead. Verified with a local `mkdocs build --strict`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRhJ5dF3N3z7X63aRaDBcQ
My previous edit tripped format-markdown-check, which gates the test jobs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QRhJ5dF3N3z7X63aRaDBcQ
…tion (#240) Migrates stocktrim-openapi-client off python-semantic-release and onto release-please in manifest mode, mirroring the topology piloted in dougborg/frontapp-openapi-client#168 and specified in dougborg/katana-openapi-client#1002 (design doc). Fixes #238 (unconstrained MCP->client dependency) in passing. Claude-Session: https://claude.ai/code/session_01QRhJ5dF3N3z7X63aRaDBcQ Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
This is the pilot for a four-repo migration off python-semantic-release / JS
semantic-releaseand onto release-please in manifest mode. The design is filedas dougborg/katana-openapi-client#1002 (§4 recommendation, §5.1–5.6 target topology,
§6 migration path). This repo was chosen as the pilot because its release pipeline
was already disabled and has never successfully run (#10) — there is nothing working
to break.
This PR does not merge or publish anything. No secrets, variables, rulesets, or
repo settings are touched. It opens a branch for review.
Before → after topology
Before (partially disabled):
release.yml(push-triggered, currentlyworkflow_dispatch-only pending #10) ranrelease-clientandrelease-mcpas twoindependent python-semantic-release jobs that each pushed a version-bump commit
straight to
main,release-mcp.ymlwas a second, overlapping tag-triggered MCPpublish surface,
release-ts.ymlran JSsemantic-releasefor the TS client, andupdate-mcp-dependency.ymlchased client releases with a follow-up commit + push tomain. Four workflows, three separate writers tomain, all guarded off because therequired secrets were never configured.
After:
release-please.yml— the only workflow that watches pushes tomain. Runsgoogleapis/release-please-action@v4in manifest mode(
release-please-config.json/.release-please-manifest.json, both at the reporoot). Opens or updates one aggregated release PR covering all three packages
(
separate-pull-requests: false); once that PR merges, createsclient-v*,mcp-v*,ts-v*tags and draft GitHub Releases atomically at the merge commit.Never pushes to
mainitself.release-pr-prepare.yml— runs only on release-please's own PR branch(
pull_requestfiltered torelease-please--*heads). Resyncsuv.lockand keepsfrontapp_mcp_server/pyproject.toml'sfrontapp-openapi-client>=Xfloor equal tothe client version the PR proposes, pushing a commit to the PR branch — this is
where the old
sync-lockfile-equivalent work andupdate-mcp-dependency.yml's jobgo, but landing atomically with the version bump instead of as a follow-up push to
main.publish.yml— the only workflow that builds and ships. Triggered exclusivelyby
client-v*/mcp-v*/ts-v*tag pushes, never by amainpush. Four jobs:publish-client(PyPI),publish-mcp(PyPI) →publish-mcp-docker(GHCR,multi-arch),
publish-ts(npm). Each builds its artifact, publishes to theregistry via OIDC, then attaches the artifact to the still-draft release, then
flips it to published.
What was deleted and why
.github/workflows/release.ymlrelease-please.yml+publish.yml; its two jobs each pushed a version-bump commit directly tomain.github/workflows/release-ts.ymlsemantic-release(version+tag only,npmPublish: false) folded into release-please +publish.yml'spublish-ts.github/workflows/release-mcp.ymlrelease.yml's MCP release surface (both could publish MCP); folded intopublish.yml'spublish-mcp/publish-mcp-docker.github/workflows/update-mcp-dependency.ymluv.lock, push tomain, re-triggerrelease.yml) is nowrelease-pr-prepare.yml, running on the release PR branch instead ofmainpackages/frontapp-client/.releaserc.json+ its@semantic-release/*/semantic-releasedevDependencies-2199lines)[tool.semantic_release]inpyproject.toml(root) andfrontapp_mcp_server/pyproject.tomlpython-semantic-releasedev dependency,frontapp-version/frontapp-releasescript entry points (semantic_release.cli:main)docs/MONOREPO_SEMANTIC_RELEASE.mddocs/RELEASE.md; nav/toctree updated inmkdocs.ymlanddocs/index.mdGrepped the repo for stray references after removal (
justfile/poe tasks, docs,tests) — see "Validation" below.
Fixes #165 — the MCP→client pin floor
frontapp_mcp_server/pyproject.tomlpinnedfrontapp-openapi-client>=0.51.0, aversion that has never been published (nothing here has ever published to
PyPI/npm at all). This is the same defect statuspro shipped and fixed in
dougborg/statuspro-openapi-client#63 — a constraint copy-pasted from a sibling repo
that automation never validated.
Chosen floor:
>=0.1.0, seeded from.release-please-manifest.json/pyproject.toml's currently-committed client version. Reasoning: release-please onlyever bumps a package's version forward from its seeded manifest value, so the
client's actual first published version can never be lower than
0.1.0— the floorcan never overshoot.
release-pr-prepare.ymlthen keeps this floor truthful on everyfuture release by rewriting it to match whatever version the client actually gets
bumped to on each release PR, so this doesn't regress into another stale pin.
Manual prerequisites before a real release (required, will otherwise fail)
No PyPI Trusted Publisher exists for
frontapp-openapi-clientorfrontapp-mcp-server, and no npm Trusted Publisher exists forfrontapp-client.publish.ymluses OIDC only (pypa/gh-action-pypi-publish,pnpm publish --provenance) — no tokens are introduced as a fallback. Until these are registered,the first real release's
publish-client/publish-mcp/publish-tsjobs willfail their registry-publish step with
Non-user identities cannot create new projects(PyPI) or the npm equivalent. This is expected, not a bug in this PR.Before merging the first release-please release PR, register:
frontapp-openapi-client, workflowpublish.yml, jobpublish-clientfrontapp-mcp-server, workflowpublish.yml, jobpublish-mcpfrontapp-client, workflowpublish.yml, jobpublish-tspublish.ymlscopes each job to a GitHub Environment (pypi-client,pypi-mcp,npm-ts) — the standard way to scope Trusted Publisher OIDC trust. Theseenvironments don't need to exist for the workflow YAML to be valid; nothing in this
PR creates or touches them (no repo-settings changes were made).
The
dougborg-release-pleaseGitHub App (ID4392719,vars.RELEASE_PLEASE_APP_ID/secrets.RELEASE_PLEASE_APP_PRIVATE_KEY) was alreadyconfigured on this repo before this PR and is used as-is by
release-please.yml/release-pr-prepare.yml— nothing new was added or rotated.Draft → upload → publish asset flow
release-please creates GitHub Releases as drafts (
"draft": trueinrelease-please-config.json). Draft releases accept asset uploads; once published arelease becomes
immutable
and permanently rejects further uploads — this is the failure mode described in
katana-openapi-client#1002 §1.4 (an MCP
.mcpbbundle permanently lost because therelease was already published when the upload was attempted).
publish.yml's jobsalways: (1) build the artifact, (2) publish it to the registry, (3)
gh release upload <tag> <artifact> --clobberonto the still-draft release, (4)gh release edit <tag> --draft=false. A release is never finalized before its assetsexist.
Bump-semantics change (behavioral)
python-semantic-release partitioned commits by scope —
(client)/(mcp)/(ts)in the commit message decided which package bumped. release-please partitions by
path — which files a commit touches decides which package(s) bump. A commit that
touches both
frontapp_mcp_server/and the repo root now bumps both packages, evenwithout an
(mcp)scope; conversely a(client)-scoped commit that happens to touchnothing under the client's tracked paths won't bump the client. Commit scopes remain
useful for changelog grouping/readability but are no longer load-bearing for version
decisions. Documented in
docs/RELEASE.mdandfrontapp_mcp_server/docs/deployment.md.Deviations from the proposal
.mcpbbundle handling. §5.4's draft-release pseudocode includes an MCPBbuild step for the MCP job. I checked whether
.mcpbbuilding exists onmainhere (issue feat(mcp): package server as MCPB (.mcpb) for one-click Claude Desktop install #102 references it) — it does not; feat(mcp): package server as MCPB (.mcpb) for one-click Claude Desktop install #102 is an open, unmerged PR
(
feat/mcp-mcpb-bundle) still mixed with unrelated changes, not something onmainto preserve. Adding net-new MCPB packaging isn't part of "rework the releaseflow" and is out of scope for this PR;
publish-mcphas no MCPB step. When feat(mcp): package server as MCPB (.mcpb) for one-click Claude Desktop install #102lands, its build step slots into
publish-mcpbefore the "attach assets" step,following the same draft→upload→publish pattern already established here.
extra-files/jsonpath for the MCP→client pin. §5.3's pseudocode does the pinbump via both
extra-filesand a scripted fallback ("belt-and-braces… in caseextra-files under-delivers", citing release-please-action#879's known
unreliability for exactly this case — a TOML dependency-array edit). I skipped the
extra-fileshalf entirely and kept only the script (release-pr-prepare.yml's"Sync MCP -> client dependency floor" step, a
tomllibread +sedrewrite). Giventhe documented unreliability, maintaining both a fragile jsonpath config and a
script that has to reconcile with it seemed like more surface area for less
reliability than the script alone. Worth revisiting if a cleaner
extra-filespattern is found in a later repo of this migration.
publish-mcp-docker,multi-arch), which §2's repo-inventory table doesn't list for frontapp (only for
katana/statuspro). It's real —
release.yml'spublish-mcp-dockerjob alreadybuilds and pushes to
ghcr.io/dougborg/frontapp-mcp-server— so I carried itforward into
publish.ymlunchanged in behavior, just re-triggered off themcp-v*tag instead ofrelease.yml's completion.python-semantic-releaseand theJS
semantic-releaseplugin stack required regeneratingpnpm-lock.yaml(a largemechanical diff,
-2199lines) anduv.lock, and runningprettier --writeon themarkdown docs I touched. Not called out in the proposal but necessary for
uv sync/
pnpm install --frozen-lockfile/poe format-checkto stay green.uv buildworkspace-output gotcha (not in the proposal, found duringvalidation):
uv buildforfrontapp_mcp_serverwrites to the workspace root'sdist/, notfrontapp_mcp_server/dist/, because it's a uv workspace member — thismatches how the old
release.ymlalready relied on it (its upload-artifact stepused a bare
dist/path aftercd frontapp_mcp_server && uv build).publish.yml'spublish-mcpjob usesuv build --package frontapp-mcp-serverfrom the repo rootand reads assets from the repo-root
dist/, verified locally.Validation
.github/workflows/, before and afterthis change (pre-existing shellcheck findings were confined to the workflow files
this PR deletes; zero findings on the three new/changed workflows).
validated against the release-please JSON schemas
(
https://raw.githubusercontent.com/googleapis/release-please/main/schemas/{config,manifest}.json,the
$schemareferenced in the config) viajsonschema.validate— both pass.Package paths (
.,frontapp_mcp_server,packages/frontapp-client) confirmedagainst the real directory layout.
uv run pytest -n 4 -m 'not docs and not schema_validation and not integration':610 passed, 1 skipped— unchanged from the baseline (this PR touches releaseconfig/docs/tooling, not product code).
uv run poe lint/uv run poe format-check: clean (ruff, ty, yamllint,prettier).
python-semantic-release,semantic_release,SEMANTIC_RELEASE_TOKEN,.releaserc, orMONOREPO_SEMANTIC_RELEASE.mdoutside this PR's ownexplanatory prose (poe tasks,
justfile-equivalent[tool.poe.tasks], docs,tests,
mkdocs.ymlnav all checked).uv build/uv build --package frontapp-mcp-server/pnpm run build+pnpm pack: all run locally to confirmpublish.yml's build steps actuallyproduce the artifacts/paths the workflow references.
What to verify on the first real run
decisions from full commit history for each package. Expect a larger first bump
and changelog than usual; review before merging.
include-component-in-tagtag format — confirm it actually producesclient-vX.Y.Z/mcp-vX.Y.Z/ts-vX.Y.Z(open question 1 in the design doc;config validates against schema but hasn't run against a live repo yet).
release-pr-prepare.ymlactually fires and commits on the real release PR(both on open and on every subsequent release-please force-push) — confirm the
uv.lock/pin-sync commit lands before merging.publish.ymljobreaches
gh release edit --draft=falseonly after its registry publish and assetupload succeed (it will currently stop short of that at the registry-publish step
until the Trusted Publisher prerequisites above are configured).
mcp-v*release — unchanged logic, but nowsourced from the tag name instead of a job output.
References
Design: dougborg/katana-openapi-client#1002. This repo: closes #10 (superseded by
this migration rather than restoring the old push triggers), fixes #165 (MCP→client
pin floor). #166 (MCP pytest
asyncio_modeconfig bug) was already fixed onmainindependently of this PR (commit
9a57510, merged before this branch was cut) — notedhere only because the task briefing called it out, not because this PR touches it.
🤖 Generated with Claude Code