You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Automate Package Publishing for All Release Artifacts #1766
Goal: Every publish is tag-triggered + keyless OIDC. no more manual cargo publish / dotnet nuget push. There's still a checklist and review wrt changelog and review cutting but this removes one step and lets us start to publish all of our packages around the same time, especially now that we've got a number of foreign language targets
crates.io — payjoin + payjoin-cli, on existing payjoin-v* / payjoin-cli-v* tags (rust-lang/crates-io-auth-action → cargo publish). Not started.
ffi python / js / dart — port the C# pattern; also unify all bindings onto the payjoin-ffi crate version (js/dart are on 0.1.1, C# on -preview.1). Not started.
Per-registry one-time setup (package owner does once, in the registry UI): create a trusted-publisher policy with Repo Owner = payjoin, Repo = rust-payjoin, Workflow File = .yml, Environment = -release. The matching GitHub -release environment gates publication with a required-reviewer approval.
Work split:
PR for workflow YAML that does publication, I'm guessing YAML for each package
Registry policy set for the package owner's account action (set up OIDC, make sure each package is bus factored correctly
Some update on this after being through the 1.1 and 1.2 trains and understanding where our automation stands.
The tail of the pipeline is ~done because everything gets shiped keyless OIDC via signed tag. Attestations are everywhere but pub.dev. Nothing in the original scope here remains.
1. Changelog lines land with the PR, under ## Unreleased in the crate's CHANGELOG.md. A small CI job fails a PR that touches a release crate's src/ without a changelog line OR a changelog: none label. The bump PR then only renames the heading and bumps numbers, and merges under it never require editing it. Goes in CONTRIBUTING.md.
2. contrib/release/tag.sh: one signed tag per crate whose manifest version is not yet tagged, after check-invariants, with the message Release <crate>-<version> and no changelog copy (that was mega redundant). Prints the push command.
3. payjoin-test-utils leaves the publish chain. payjoin-cli and payjoin-mailroom depend on it only as a dev-dependency; making those path-only means cargo strips them on publish, so test-utils no longer needs a crates.io release before its dependents and the dev-dependency cycle with mailroom goes away.
4. payjoin-ffi and its language packages share MAJOR.MINOR, taken from payjoin-ffi/Cargo.toml and enforced by check-invariants; the patch number is per language. An ffi release resets every package to .0 and one payjoin-ffi-X.Y.0 tag fans out to the four registries. A fix in one wrapper (a vulnerable npm dependency, a wheel build image) bumps that package's patch alone and goes out on its existing payjoin-<lang>-X.Y.Z tag. Full lockstep does not work because npm has no post-release slot. The +payjoin-X build metadata goes away; PyPI rejects it, npm and NuGet ignore it, and pub.dev orders by it. Dart's git pin becomes the ffi tag.
5. Kotlin has no version and no publish job. Maven Central has no trusted publishing, so this is the one place a stored token and signing key are unavoidable. Separate issue once we decide it is still wanted.
6. cargo-semver-checks on every PR rather than only bump PRs.
7. Rewrite .github/ISSUE_TEMPLATE/minor-release.md to match: the changelog comes from ## Unreleased, the tag comes from tag.sh, the tag message no longer carries a copy of the changelog, and the bindings and dependent-crate steps are listed after the payjoin release instead of implied.
Not planned: a bot-maintained release PR (release-plz). Once 1 and 2 exist the bump PR is small enough that a script covers it.
Ok so now that #1942 is merged, a minor release with what's on master today looks like this from a high level:
Wave 1: one PR running bump.sh payjoin X. Review, merge, one tag.sh, one push, one approval.
Wave 2: one PR bumping cli, mailroom, the bindings and the Dart pin to wave 1's tag commit. Review, merge, one tag.sh --bindings, 2–3 pushes, 6+ approvals.
One approval per wave is how this gets less tedious than the current circumstance.
Then, for payjoin-ffi, the thing to do is for all of the bindings to share the same version. If just one language version needs an update, it's not the end of the world to just bump payjoin-ffi and have all the others get released. Then "wave 2" includes payjoin-ffi tagging and one push.
Then bump.sh would need to be extended, to support bindings + dart pin.
unifying the bindings to all share one version/dropping -payjoin-x.y.z suffix (that breaks semver for those packages)
I don't think the suffix breaks semver (Semver 2.0 allows build metadata ). The argument for dropping it is that it's informational only and is not supported by every package manager in the languages we support, so it's dropped from NPM and pypi already. And it does add some complexity in parsing and version checking, so I'm not super attached to it but also don't think it's particularly urgent to drop.
Activity
spacebear21 commentedon Jul 27, 2026
My preference would be to reorder the milestones:
i.e. I'd rather start with the workflow I'm most familiar with, systematize that, then fan out with more complex ffi workflows.
DanGould commentedon Oct 2, 2026
Some update on this after being through the 1.1 and 1.2 trains and understanding where our automation stands.
The tail of the pipeline is ~done because everything gets shiped keyless OIDC via signed tag. Attestations are everywhere but pub.dev. Nothing in the original scope here remains.
Friction remains before the tag. Each train is a stack of per-crate bump PRs with changelogs written at release time which means ANY merge to master under the stack means editing a layer of changelog and restacking. The Dart package in #1935 pins a‼️ https://github.com/payjoin/rust-payjoin/pull/1935/changes#diff-e75b0dbe17d46e8cc6cd9d55212483bb7f41ecc568de2d5af5754a50aa287b10R21 which is a mistake from doing things manually.
revfrom a restacked commit that no longer exists in any PRRemaining work, in the order I plan to do it:
## Unreleasedin the crate's CHANGELOG.md. A small CI job fails a PR that touches a release crate'ssrc/without a changelog line OR achangelog: nonelabel. The bump PR then only renames the heading and bumps numbers, and merges under it never require editing it. Goes in CONTRIBUTING.md.contrib/release/tag.sh: one signed tag per crate whose manifest version is not yet tagged, after check-invariants, with the messageRelease <crate>-<version>and no changelog copy (that was mega redundant). Prints the push command.MAJOR.MINOR, taken frompayjoin-ffi/Cargo.tomland enforced by check-invariants; the patch number is per language. An ffi release resets every package to.0and onepayjoin-ffi-X.Y.0tag fans out to the four registries. A fix in one wrapper (a vulnerable npm dependency, a wheel build image) bumps that package's patch alone and goes out on its existingpayjoin-<lang>-X.Y.Ztag. Full lockstep does not work because npm has no post-release slot. The+payjoin-Xbuild metadata goes away; PyPI rejects it, npm and NuGet ignore it, and pub.dev orders by it. Dart's git pin becomes the ffi tag..github/ISSUE_TEMPLATE/minor-release.mdto match: the changelog comes from## Unreleased, the tag comes fromtag.sh, the tag message no longer carries a copy of the changelog, and the bindings and dependent-crate steps are listed after thepayjoinrelease instead of implied.Not planned: a bot-maintained release PR (release-plz). Once 1 and 2 exist the bump PR is small enough that a script covers it.
DanGould commentedon Oct 5, 2026
Ok so now that #1942 is merged, a minor release with what's on master today looks like this from a high level:
One approval per wave is how this gets less tedious than the current circumstance.
DanGould commentedon Oct 5, 2026
Then, for payjoin-ffi, the thing to do is for all of the bindings to share the same version. If just one language version needs an update, it's not the end of the world to just bump payjoin-ffi and have all the others get released. Then "wave 2" includes payjoin-ffi tagging and one push.
Then bump.sh would need to be extended, to support bindings + dart pin.
DanGould commentedon Oct 15, 2026
I think some things might still be missing
Please close this if I'm mistaken
spacebear21 commentedon Oct 15, 2026
Yes, this should still be done.
I don't think the suffix breaks semver (Semver 2.0 allows build metadata ). The argument for dropping it is that it's informational only and is not supported by every package manager in the languages we support, so it's dropped from NPM and pypi already. And it does add some complexity in parsing and version checking, so I'm not super attached to it but also don't think it's particularly urgent to drop.
Teach bump.sh to bump the language bindings