diff --git a/.github/ISSUE_TEMPLATE/minor-release.md b/.github/ISSUE_TEMPLATE/minor-release.md index d77f7000f..a45dba8ef 100644 --- a/.github/ISSUE_TEMPLATE/minor-release.md +++ b/.github/ISSUE_TEMPLATE/minor-release.md @@ -29,6 +29,10 @@ landed since the last release, so there is nothing to compile here. `cargo +nightly`, so run it where rustup has a nightly toolchain. `nix develop .#release` has jq but no rustup; there, pass `--no-lock`, then run `contrib/update-lock-files.sh` from a rustup shell and `contrib/release/check-invariants.sh CRATE` again. +- [ ] For a `payjoin` bump, prepare the common bindings version bump in this PR + as well, following the [bindings instructions](../../payjoin-ffi/RELEASING.md). + The checker requires the FFI core dependency and binding metadata to agree. + Publish the Rust core before pushing the corresponding FFI tag. - [ ] Read the new changelog section once; fix wording, do not add history. - [ ] One commit, "Bump CRATE version to MAJOR.MINOR+1.0". Open the PR against `master`. The `Check release version bump` job runs check-invariants, a publish dry run and, @@ -51,10 +55,11 @@ changelog line. The bump PR is not edited for it. #### After a `payjoin` release -- [ ] Open the bindings bump PR (python, javascript, csharp, dart manifests and changelogs; - Dart's `native/Cargo.toml` pins the `payjoin-MAJOR.MINOR+1.0` tag commit, never a - branch commit). Merge, then run - `nix develop .#release -c contrib/release/tag.sh --bindings` and push the tags. +- [ ] Confirm the prepared bindings bump is merged and follow the + [bindings release instructions](../../payjoin-ffi/RELEASING.md). Dart's + published native source is pinned automatically to the release checkout. + Create one signed tag with + `nix develop .#release -c contrib/release/tag.sh payjoin-ffi`, then push it. - [ ] Bump dependent release crates (`payjoin-cli`, `payjoin-mailroom`) on their own schedule, each with this checklist. Their tags verify that `payjoin` is already on crates.io. diff --git a/.github/actions/verify-tag-version/action.yml b/.github/actions/verify-tag-version/action.yml deleted file mode 100644 index 437de3c5a..000000000 --- a/.github/actions/verify-tag-version/action.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Verify tag version -description: > - Check that the pushed tag is the expected prefix followed by the version - about to be published, refusing to publish on any mismatch. Tags carry - the full `{version}+payjoin-{version}` string; when the packed version - has no build metadata (npm, PyPI, and NuGet artifacts cannot carry it), - the tag's metadata is stripped before comparing. -inputs: - tag-prefix: - description: Expected tag prefix, e.g. payjoin-csharp- - required: true - version: - description: Version of the artifact or package about to be published - required: true -outputs: - version: - description: The version the tag calls for - value: ${{ steps.check.outputs.version }} -runs: - using: composite - steps: - - name: Compare tag against version - id: check - shell: bash - env: - TAG: ${{ github.ref_name }} - PREFIX: ${{ inputs.tag-prefix }} - VERSION: ${{ inputs.version }} - run: | - set -euo pipefail - if [[ $TAG != "$PREFIX"* ]]; then - echo "::error::tag $TAG does not start with $PREFIX; refusing to publish" - exit 1 - fi - tag_version="${TAG#"$PREFIX"}" - compare_version="$tag_version" - # A packed version without build metadata comes from a registry - # that cannot represent it, so the tag is compared with its - # metadata stripped; a packed version that carries metadata must - # match the tag exactly. - if [[ $VERSION != *+* ]]; then - compare_version="${tag_version%%+*}" - fi - if [[ $compare_version != "$VERSION" ]]; then - echo "::error::tag $TAG implies version $compare_version but the packed version is $VERSION; refusing to publish" - exit 1 - fi - echo "version=$tag_version" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/bindings-release.yml b/.github/workflows/bindings-release.yml new file mode 100644 index 000000000..18e782f97 --- /dev/null +++ b/.github/workflows/bindings-release.yml @@ -0,0 +1,311 @@ +name: Release language bindings + +# Pushing a signed `payjoin-ffi-+payjoin-` tag verifies the +# tag and the shared binding version, runs every language's build, package +# and smoke checks at that commit, then publishes Python, JavaScript, C#, +# and Dart through the `release` environment (required reviewer) with +# keyless OIDC and cuts one GitHub release holding every package. +# +# Each registry has its own publish job. All four wait on every language +# build, so they reach the environment gate together and one approval +# releases them all; a registry failure is retried with "Re-run failed +# jobs" on the same run, reusing the tested artifacts. + +on: + push: + tags: + - "payjoin-ffi-[0-9]*" + +# Keep two attempts at the same tag from publishing concurrently. +concurrency: + group: bindings-release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + verify-tag: + name: Verify release tag + runs-on: ubuntu-26.04 + outputs: + version: ${{ steps.meta.outputs.version }} + prerelease: ${{ steps.meta.outputs.prerelease }} + steps: + - name: Checkout repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Fetch master + run: git fetch --no-tags origin +refs/heads/master:refs/remotes/origin/master + - name: Set up nix + uses: ./.github/actions/setup-nix + - name: Verify tag hygiene + env: + TAG: ${{ github.ref_name }} + run: nix --quiet develop .#release -c ./contrib/release/verify-tag-hygiene.sh "$TAG" + - name: Verify every binding carries the tagged version + id: meta + env: + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + version="$(nix --quiet develop .#release -c ./contrib/release/bindings-version.sh --tag "$TAG")" + nix --quiet develop .#release -c ./contrib/release/check-invariants.sh payjoin-ffi + source contrib/release/crates.sh + { + echo "version=$version" + # A hyphen in the version proper marks a pre-release; the + # +payjoin- build metadata never does. + is_prerelease "${version%%+*}" && echo "prerelease=true" || echo "prerelease=false" + } >>"$GITHUB_OUTPUT" + - name: Verify the wrapped payjoin core is on crates.io + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + set -euo pipefail + source contrib/release/crates.sh + core="${VERSION##*+payjoin-}" + # Dart consumers build the bindings from source against it. + crate_published payjoin "$core" || { + echo "::error::payjoin $core is not on crates.io; publish it before the bindings" + exit 1 + } + + python: + needs: verify-tag + uses: ./.github/workflows/python.yml + javascript: + needs: verify-tag + uses: ./.github/workflows/javascript.yml + csharp: + needs: verify-tag + uses: ./.github/workflows/csharp.yml + dart: + needs: verify-tag + uses: ./.github/workflows/dart.yml + kotlin: + needs: verify-tag + uses: ./.github/workflows/kotlin.yml + + publish-python: + name: Publish to PyPI + needs: [python, javascript, csharp, dart, kotlin] + runs-on: ubuntu-26.04 + environment: release + permissions: + id-token: write # OIDC: PyPI trusted publishing and its PEP 740 attestations + attestations: write # actions/attest-build-provenance writes the attestation + steps: + - name: Download wheels + uses: actions/download-artifact@v4 + with: + pattern: payjoin-python-wheel-* + merge-multiple: true + path: dist + - name: Check both wheels are present + run: | + set -euo pipefail + shopt -s nullglob + pkgs=(dist/payjoin-*.whl) + if [ "${#pkgs[@]}" -ne 2 ]; then + echo "::error::expected exactly two wheels in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" + exit 1 + fi + - name: Attest build provenance + # A consumer runs: gh attestation verify .whl -R payjoin/rust-payjoin + uses: actions/attest-build-provenance@v4 + with: + subject-path: dist/*.whl + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist + + publish-javascript: + name: Publish to npmjs.com + needs: [verify-tag, python, javascript, csharp, dart, kotlin] + runs-on: ubuntu-26.04 + environment: release + permissions: + id-token: write # OIDC: npm trusted publishing and its provenance statement + attestations: write # actions/attest writes the attestation + steps: + - name: Download npm package + uses: actions/download-artifact@v4 + with: + name: payjoin-javascript-npm-package + path: dist + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: https://registry.npmjs.org + - name: Update npm + run: | + npm install -g npm@11 + npm --version + - name: Locate the tarball + id: locate + run: | + set -euo pipefail + shopt -s nullglob + pkgs=(dist/payjoin-*.tgz) + if [ "${#pkgs[@]}" -ne 1 ]; then + echo "::error::expected exactly one .tgz in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" + exit 1 + fi + echo "tarball=${pkgs[0]}" >>"$GITHUB_OUTPUT" + - name: Attest build provenance + # A consumer runs: gh attestation verify .tgz -R payjoin/rust-payjoin + uses: actions/attest@v4 + with: + subject-path: ${{ steps.locate.outputs.tarball }} + - name: Publish to npmjs.com + env: + TARBALL: ${{ steps.locate.outputs.tarball }} + # Pre-releases go to the `next` dist-tag so `npm install payjoin` + # keeps resolving the latest stable version. + CHANNEL: ${{ needs.verify-tag.outputs.prerelease == 'true' && 'next' || 'latest' }} + run: npm publish "./$TARBALL" --tag "$CHANNEL" + + publish-csharp: + name: Publish to nuget.org + needs: [python, javascript, csharp, dart, kotlin] + runs-on: ubuntu-26.04 + environment: release + permissions: + id-token: write # OIDC: used by both NuGet/login and actions/attest + attestations: write # actions/attest writes the attestation + steps: + - name: Download NuGet package + uses: actions/download-artifact@v4 + with: + name: payjoin-csharp-nuget-package + path: dist + - name: Install .NET 10 SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + - name: Locate the package + id: locate + run: | + set -euo pipefail + shopt -s nullglob + pkgs=(dist/*.nupkg) + if [ "${#pkgs[@]}" -ne 1 ]; then + echo "::error::expected exactly one .nupkg in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" + exit 1 + fi + echo "nupkg=${pkgs[0]}" >>"$GITHUB_OUTPUT" + - name: Attest build provenance + # Attesting the .nupkg covers every RID native lib inside it; a consumer + # runs: gh attestation verify .nupkg -R payjoin/rust-payjoin + uses: actions/attest@v4 + with: + subject-path: ${{ steps.locate.outputs.nupkg }} + - name: NuGet login (OIDC -> short-lived API key) + id: login + uses: NuGet/login@v1 + with: + user: ${{ secrets.NUGET_USER }} + - name: Push to nuget.org + env: + NUPKG: ${{ steps.locate.outputs.nupkg }} + NUGET_API_KEY: ${{ steps.login.outputs.NUGET_API_KEY }} + run: | + dotnet nuget push "$NUPKG" \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --no-symbols \ + --skip-duplicate + + publish-dart: + name: Publish to pub.dev + needs: [python, javascript, csharp, dart, kotlin] + runs-on: ubuntu-26.04 + environment: release + permissions: + id-token: write # OIDC: pub.dev automated publishing + contents: read + steps: + - name: Checkout repo + uses: actions/checkout@v6 + - name: Set up nix + uses: ./.github/actions/setup-nix + - name: Download the staged package + uses: actions/download-artifact@v4 + with: + name: payjoin-dart-pub-package + path: dist + - name: Extract the staged package + run: | + mkdir -p "$RUNNER_TEMP/payjoin-pub" + tar -xzf dist/payjoin-dart.tar.gz -C "$RUNNER_TEMP/payjoin-pub" + - name: Publish to pub.dev + run: nix develop .#dart -c ./payjoin-ffi/dart/contrib/publish.sh "$RUNNER_TEMP/payjoin-pub" + + github-release: + name: Create GitHub release + needs: + [ + verify-tag, + publish-python, + publish-javascript, + publish-csharp, + publish-dart, + ] + runs-on: ubuntu-26.04 + permissions: + contents: write # create the Release for this tag and upload assets + env: + VERSION: ${{ needs.verify-tag.outputs.version }} + steps: + - name: Checkout repo + uses: actions/checkout@v6 + - name: Download every package + uses: actions/download-artifact@v4 + with: + pattern: payjoin-* + merge-multiple: true + path: dist + - name: Check the asset set is complete + run: | + set -euo pipefail + shopt -s nullglob + wheels=(dist/*.whl) + npm=(dist/*.tgz) + nuget=(dist/*.nupkg) + if [[ ${#wheels[@]} != 2 || ${#npm[@]} != 1 || ${#nuget[@]} != 1 || ! -f dist/payjoin-dart.tar.gz ]]; then + echo "::error::expected two wheels, one npm tarball, one NuGet package, and the Dart archive in dist/" + ls dist + exit 1 + fi + - name: Generate SHA256SUMS + # The NuGet package's native libraries are listed individually so + # each can be verified on its own. A maintainer's GPG signature over + # SHA256SUMS is made locally and uploaded as SHA256SUMS.asc; no + # signing key reaches a runner. + env: + ARCHIVE_GLOBS: "*.nupkg" + MEMBER_GLOBS: runtimes/*/native/* + run: | + ./contrib/release/generate-sha256sums.sh dist + cat dist/SHA256SUMS + - name: Extract changelog section + id: changelog + run: | + { + echo "body<>"$GITHUB_OUTPUT" + - name: Create release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} + body: ${{ steps.changelog.outputs.body }} + prerelease: ${{ needs.verify-tag.outputs.prerelease }} + fail_on_unmatched_files: true + files: dist/* diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 8e4846460..418775d9f 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -12,9 +12,7 @@ on: # the flake change this workflow's environment. - flake.nix - flake.lock - push: - tags: - - "payjoin-csharp-[0-9]*" + workflow_call: jobs: build-csharp-and-test-unix: @@ -252,92 +250,3 @@ jobs: # 'auto' derives the version from the packed artifact, so Payjoin.csproj # stays the only place the package version is maintained. run: bash ./scripts/smoke_nuget_package.sh artifacts/packages auto ${{ matrix.rid }} - - verify-tag: - name: "Verify release tag" - if: startsWith(github.ref, 'refs/tags/payjoin-csharp-') - permissions: - contents: read - uses: ./.github/workflows/verify-tag-hygiene.yml - - publish-nuget: - name: "Publish to nuget.org (trusted publishing / OIDC)" - runs-on: ubuntu-latest - needs: [pack-nuget, smoke-nuget, verify-tag] - if: startsWith(github.ref, 'refs/tags/payjoin-csharp-') - environment: release - permissions: - id-token: write # OIDC: used by BOTH NuGet/login and actions/attest - attestations: write # actions/attest writes the attestation - contents: read # needed only to check out the in-repo verify-tag-version action - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download packed NuGet package - uses: actions/download-artifact@v4 - with: - name: payjoin-csharp-nuget-package - path: dist - - - name: Install .NET 10 SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.x" - - - name: Locate packed artifact - id: locate - shell: bash - run: | - set -euo pipefail - shopt -s nullglob - pkgs=(dist/*.nupkg) - if [ "${#pkgs[@]}" -ne 1 ]; then - echo "::error::expected exactly one .nupkg in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" - exit 1 - fi - # Payjoin.0.24.0-preview.1.nupkg -> 0.24.0-preview.1 - name="$(basename "${pkgs[0]}" .nupkg)" - echo "nupkg=${pkgs[0]}" >> "$GITHUB_OUTPUT" - echo "version=${name#Payjoin.}" >> "$GITHUB_OUTPUT" - - - name: Verify tag matches packed artifact version - id: verify - uses: ./.github/actions/verify-tag-version - with: - tag-prefix: payjoin-csharp- - version: ${{ steps.locate.outputs.version }} - - - name: Attest build provenance (nupkg) - # Attesting the .nupkg covers every RID native lib inside it; a consumer - # runs: gh attestation verify .nupkg -R payjoin/rust-payjoin - uses: actions/attest@v4 - with: - subject-path: ${{ steps.locate.outputs.nupkg }} - - - name: NuGet login (OIDC -> short-lived API key) - id: login - uses: NuGet/login@v1 - with: - user: ${{ secrets.NUGET_USER }} - - - name: Push to nuget.org - run: | - dotnet nuget push "${{ steps.locate.outputs.nupkg }}" \ - --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \ - --source https://api.nuget.org/v3/index.json \ - --no-symbols \ - --skip-duplicate - - github-release: - name: "Attach nupkg + SHA256SUMS to the GitHub release" - needs: [publish-nuget] - if: startsWith(github.ref, 'refs/tags/payjoin-csharp-') - permissions: - contents: write # create/update the Release for this tag and upload assets - uses: ./.github/workflows/release-assets.yml - with: - artifact-pattern: payjoin-csharp-nuget-package - tag-prefix: payjoin-csharp- - archive-globs: "*.nupkg" - archive-member-globs: runtimes/*/native/* diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index d8dc44124..9ea38d7b0 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -12,9 +12,7 @@ on: # change this workflow's environment. - flake.nix - flake.lock - push: - tags: - - "payjoin-dart-[0-9]*" + workflow_call: jobs: build-dart-and-test: @@ -50,53 +48,14 @@ jobs: - name: Set up nix uses: ./.github/actions/setup-nix - name: Generate production bindings and dry-run publish - run: nix develop .#dart -c ./payjoin-ffi/dart/contrib/prepare-publish.sh - - verify-tag: - name: "Verify release tag" - if: startsWith(github.ref, 'refs/tags/payjoin-dart-') - permissions: - contents: read - uses: ./.github/workflows/verify-tag-hygiene.yml - - publish-pub: - name: "Publish to pub.dev (automated publishing / OIDC)" - runs-on: ubuntu-26.04 - needs: [build-dart-and-test, verify-pub-package, verify-tag] - if: startsWith(github.ref, 'refs/tags/payjoin-dart-') - environment: release - permissions: - id-token: write # OIDC: pub.dev automated publishing - contents: read - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: "Use cache" - uses: Swatinem/rust-cache@v2 - - name: Set up nix - uses: ./.github/actions/setup-nix - - - name: Read pubspec version - id: pubspec - shell: bash - run: | - set -euo pipefail - version="$(sed -n 's/^version: *//p' payjoin-ffi/dart/pubspec.yaml)" - if [ -z "$version" ]; then - echo "::error::could not read version from pubspec.yaml" - exit 1 - fi - echo "version=$version" >>"$GITHUB_OUTPUT" - - - name: Verify tag matches pubspec version - uses: ./.github/actions/verify-tag-version + run: nix develop .#dart -c ./payjoin-ffi/dart/contrib/prepare-publish.sh "$RUNNER_TEMP/payjoin-pub" + # The release workflow publishes this exact stage, so it is archived + # rather than regenerated in the publish job. + - name: Archive the staged package + run: tar -czf "$RUNNER_TEMP/payjoin-dart.tar.gz" -C "$RUNNER_TEMP/payjoin-pub" . + - name: Upload the staged package + uses: actions/upload-artifact@v4 with: - tag-prefix: payjoin-dart- - version: ${{ steps.pubspec.outputs.version }} - - - name: Prepare the package - run: nix develop .#dart -c ./payjoin-ffi/dart/contrib/prepare-publish.sh - - - name: Publish to pub.dev - run: nix develop .#dart -c ./payjoin-ffi/dart/contrib/publish.sh + name: payjoin-dart-pub-package + path: ${{ runner.temp }}/payjoin-dart.tar.gz + if-no-files-found: error diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 845fcbe9c..2d5ff13b4 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -12,9 +12,7 @@ on: # change this workflow's environment. - flake.nix - flake.lock - push: - tags: - - "payjoin-javascript-[0-9]*" + workflow_call: jobs: build-js-and-test: @@ -90,83 +88,3 @@ jobs: payjoin.Url.parse("https://example.com"); console.log("smoke ok"); ' - - verify-tag: - name: "Verify release tag" - if: startsWith(github.ref, 'refs/tags/payjoin-javascript-') - permissions: - contents: read - uses: ./.github/workflows/verify-tag-hygiene.yml - - publish-npm: - name: "Publish to npmjs.com (trusted publishing / OIDC)" - runs-on: ubuntu-26.04 - needs: [build-js-and-test, pack-npm, smoke-npm, verify-tag] - if: startsWith(github.ref, 'refs/tags/payjoin-javascript-') - environment: release - permissions: - id-token: write # OIDC: npm trusted publishing and its provenance statement - attestations: write # actions/attest writes the attestation - contents: read # needed only to check out the in-repo verify-tag-version action - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download npm package - uses: actions/download-artifact@v4 - with: - name: payjoin-javascript-npm-package - path: dist - - - name: Install Node - uses: actions/setup-node@v4 - with: - node-version: "24" - registry-url: https://registry.npmjs.org - - - name: Update npm - run: | - npm install -g npm@11 - npm --version - - - name: Locate packed artifact - id: locate - shell: bash - run: | - set -euo pipefail - shopt -s nullglob - pkgs=(dist/payjoin-*.tgz) - if [ "${#pkgs[@]}" -ne 1 ]; then - echo "::error::expected exactly one .tgz in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" - exit 1 - fi - # payjoin-0.1.1.tgz -> 0.1.1 - name="$(basename "${pkgs[0]}" .tgz)" - echo "tarball=${pkgs[0]}" >> "$GITHUB_OUTPUT" - echo "version=${name#payjoin-}" >> "$GITHUB_OUTPUT" - - - name: Verify tag matches packed artifact version - uses: ./.github/actions/verify-tag-version - with: - tag-prefix: payjoin-javascript- - version: ${{ steps.locate.outputs.version }} - - - name: Attest build provenance (tarball) - # A consumer runs: gh attestation verify .tgz -R payjoin/rust-payjoin - uses: actions/attest@v4 - with: - subject-path: ${{ steps.locate.outputs.tarball }} - - - name: Publish to npmjs.com - run: npm publish "./${{ steps.locate.outputs.tarball }}" - - github-release: - name: "Attach tarball + SHA256SUMS to the GitHub release" - needs: [publish-npm] - if: startsWith(github.ref, 'refs/tags/payjoin-javascript-') - permissions: - contents: write # create/update the Release for this tag and upload assets - uses: ./.github/workflows/release-assets.yml - with: - artifact-pattern: payjoin-javascript-npm-package - tag-prefix: payjoin-javascript- diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index a6fe71437..cad8b004e 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -13,6 +13,7 @@ on: - flake.nix - flake.lock - .github/workflows/kotlin.yml + workflow_call: jobs: build-kotlin-and-test: diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 144b228f5..537dd6957 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,9 +12,7 @@ on: # change this workflow's environment. - flake.nix - flake.lock - push: - tags: - - "payjoin-python-[0-9]*" + workflow_call: jobs: build-python-and-test: @@ -110,78 +108,3 @@ jobs: payjoin.Uri.parse("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com") print("smoke ok") ' - - verify-tag: - name: "Verify release tag" - if: startsWith(github.ref, 'refs/tags/payjoin-python-') - permissions: - contents: read - uses: ./.github/workflows/verify-tag-hygiene.yml - - publish-pypi: - name: "Publish to PyPI (trusted publishing / OIDC)" - runs-on: ubuntu-26.04 - needs: [build-python-and-test, build-wheel, smoke-wheel, verify-tag] - if: startsWith(github.ref, 'refs/tags/payjoin-python-') - environment: release - permissions: - id-token: write # OIDC: PyPI trusted publishing and its PEP 740 attestations - attestations: write # actions/attest-build-provenance writes the attestation - contents: read # needed only to check out the in-repo verify-tag-version action - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Download wheels - uses: actions/download-artifact@v4 - with: - pattern: payjoin-python-wheel-* - merge-multiple: true - path: dist - - - name: Locate packed artifacts - id: locate - shell: bash - run: | - set -euo pipefail - shopt -s nullglob - pkgs=(dist/payjoin-*.whl) - if [ "${#pkgs[@]}" -ne 2 ]; then - echo "::error::expected exactly two wheels in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" - exit 1 - fi - # payjoin-0.24.0-py3-none-.whl -> 0.24.0 - versions="$(for pkg in "${pkgs[@]}"; do basename "$pkg" | cut -d- -f2; done | sort -u)" - if [ "$(echo "$versions" | wc -l)" -ne 1 ]; then - echo "::error::wheels disagree on version: $versions; refusing to publish" - exit 1 - fi - echo "version=$versions" >> "$GITHUB_OUTPUT" - - - name: Verify tag matches packed artifact version - uses: ./.github/actions/verify-tag-version - with: - tag-prefix: payjoin-python- - version: ${{ steps.locate.outputs.version }} - - - name: Attest build provenance (wheels) - # A consumer runs: gh attestation verify .whl -R payjoin/rust-payjoin - uses: actions/attest-build-provenance@v4 - with: - subject-path: dist/*.whl - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: dist - - github-release: - name: "Attach wheels + SHA256SUMS to the GitHub release" - needs: [publish-pypi] - if: startsWith(github.ref, 'refs/tags/payjoin-python-') - permissions: - contents: write # create/update the Release for this tag and upload assets - uses: ./.github/workflows/release-assets.yml - with: - artifact-pattern: payjoin-python-wheel-* - tag-prefix: payjoin-python- diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml deleted file mode 100644 index 3888fe07b..000000000 --- a/.github/workflows/release-assets.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Attach release assets - -# Reusable tail of every binding publish workflow: download the packed -# artifact(s), generate SHA256SUMS, and attach everything to the GitHub -# release for the pushed tag. A maintainer's GPG detached signature over -# SHA256SUMS is added out-of-band (locally, then uploaded as -# SHA256SUMS.asc), so no signing key ever reaches a runner. - -on: - workflow_call: - inputs: - artifact-pattern: - description: Name (or glob) of the artifact(s) holding the files to attach - required: true - type: string - tag-prefix: - description: > - Tag prefix before the version, e.g. payjoin-csharp-. Used to - derive the version for pre-release detection. - required: true - type: string - archive-globs: - description: > - Optional whitespace-separated globs naming which downloaded - assets are archives to open for member hashing. Required with - archive-member-globs. - required: false - type: string - default: "" - archive-member-globs: - description: > - Optional whitespace-separated globs of files inside each - matched archive to hash into SHA256SUMS, so files nested - inside a package (e.g. native libraries) can be verified - individually. Required with archive-globs. - required: false - type: string - default: "" - -jobs: - github-release: - name: Create GitHub release - runs-on: ubuntu-26.04 - permissions: - contents: write # create/update the Release for this tag and upload assets - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - - name: Download release artifacts - uses: actions/download-artifact@v4 - with: - pattern: ${{ inputs.artifact-pattern }} - merge-multiple: true - path: dist - - - name: Generate SHA256SUMS - shell: bash - env: - ARCHIVE_GLOBS: ${{ inputs.archive-globs }} - MEMBER_GLOBS: ${{ inputs.archive-member-globs }} - run: | - ./contrib/release/generate-sha256sums.sh dist - echo "----- SHA256SUMS -----" - cat dist/SHA256SUMS - - - name: Derive pre-release status from the tag - id: meta - shell: bash - env: - TAG: ${{ github.ref_name }} - PREFIX: ${{ inputs.tag-prefix }} - run: | - set -euo pipefail - version="${TAG#"$PREFIX"}" - # SemVer: a hyphen in the version proper marks a pre-release; - # build metadata after a + never does. - version="${version%%+*}" - if [[ $version == *-* ]]; then - echo "prerelease=true" >>"$GITHUB_OUTPUT" - else - echo "prerelease=false" >>"$GITHUB_OUTPUT" - fi - - - name: Create / update GitHub release - uses: softprops/action-gh-release@v3 - with: - files: dist/* - fail_on_unmatched_files: true - draft: false - prerelease: ${{ steps.meta.outputs.prerelease }} diff --git a/.github/workflows/verify-tag-hygiene.yml b/.github/workflows/verify-tag-hygiene.yml deleted file mode 100644 index d80900fa6..000000000 --- a/.github/workflows/verify-tag-hygiene.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Verify tag hygiene - -# Reusable head of every binding publish workflow: verify the pushed tag is -# annotated, signed by a maintainer key in contrib/release/keys/, and an -# ancestor of master, so only a maintainer can cut a release. Call it as a -# job the publish job `needs`, so a bad tag fails before the `release` -# environment asks for approval. The tag comes from the triggering ref; the -# checks are prefix-agnostic, so there are no inputs. - -on: - workflow_call: - -jobs: - verify-tag-hygiene: - name: Verify tag hygiene - runs-on: ubuntu-26.04 - permissions: - contents: read - steps: - - name: Checkout repo - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Fetch master - run: git fetch --no-tags origin +refs/heads/master:refs/remotes/origin/master - - name: Set up nix - uses: ./.github/actions/setup-nix - - name: Verify tag hygiene - env: - TAG: ${{ github.ref_name }} - run: nix --quiet develop .#release -c ./contrib/release/verify-tag-hygiene.sh "$TAG" diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 0791c2824..77e5a70c3 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -2368,7 +2368,7 @@ dependencies = [ [[package]] name = "payjoin-ffi" -version = "0.24.0" +version = "0.25.0" dependencies = [ "async-trait", "bdk_wallet", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index b4068625f..690b77535 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -2568,7 +2568,7 @@ dependencies = [ [[package]] name = "payjoin-ffi" -version = "0.24.0" +version = "0.25.0" dependencies = [ "async-trait", "bdk_wallet", diff --git a/contrib/release/bindings-version.sh b/contrib/release/bindings-version.sh new file mode 100755 index 000000000..5435643b3 --- /dev/null +++ b/contrib/release/bindings-version.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# Check that every binding package carries the shared payjoin-ffi version +# and print the version a payjoin-ffi release tag names: +# +payjoin-. With --tag , also require +# that exact tag. +# +# PyPI and npm cannot carry build metadata, so their manifests hold the +# bare version and package.json records the full one in releaseTag. +# Python spells a -rc.N pre-release as rcN (PEP 440). +# +# usage: bindings-version.sh [--tag ] +set -euo pipefail +# shellcheck source=contrib/release/crates.sh +source "$(dirname "${BASH_SOURCE[0]}")/crates.sh" +cd "$REPO_ROOT" + +tag="" +case "${1:-}" in + "") ;; + --tag) tag="${2:?usage: bindings-version.sh [--tag ]}" ;; + *) + echo "usage: bindings-version.sh [--tag ]" >&2 + exit 1 + ;; +esac +die() { + echo "bindings-version: $*" >&2 + exit 1 +} + +RELEASE_METADATA="$(cargo_metadata)" +ffi="$(manifest_version payjoin-ffi)" +core="$(manifest_version payjoin)" +[[ $ffi =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]] || + die "payjoin-ffi version $ffi must be MAJOR.MINOR.PATCH, optionally followed by -rc.N" +full="$ffi+payjoin-$core" +python="${ffi/-rc./rc}" + +status=0 +expect() { # + [ "$2" = "$3" ] || { + echo "bindings-version: $1 is ${3:-missing}, expected $2" >&2 + status=1 + } +} +expect "payjoin-ffi's payjoin requirement" "$core" \ + "$(cargo_metadata | jq -r '.packages[] | select(.name == "payjoin-ffi") + | .dependencies[] | select(.name == "payjoin") + | .req | ltrimstr("^") | ltrimstr("=")')" +expect "python/pyproject.toml version" "$python" \ + "$(sed -n 's/^version = "\(.*\)"$/\1/p' payjoin-ffi/python/pyproject.toml | head -1)" +expect "javascript/package.json version" "$ffi" \ + "$(jq -r .version payjoin-ffi/javascript/package.json)" +expect "javascript/package.json releaseTag" "$full" \ + "$(jq -r .releaseTag payjoin-ffi/javascript/package.json)" +expect "dart/pubspec.yaml version" "$full" \ + "$(sed -n 's/^version: *//p' payjoin-ffi/dart/pubspec.yaml)" +expect "csharp/Payjoin.csproj Version" "$full" \ + "$(sed -n 's/.*\(.*\)<\/Version>.*/\1/p' payjoin-ffi/csharp/Payjoin.csproj | head -1)" +[ -z "$tag" ] || expect "release tag" "payjoin-ffi-$full" "$tag" +[ "$status" -eq 0 ] || exit 1 +printf '%s\n' "$full" diff --git a/contrib/release/check-invariants.sh b/contrib/release/check-invariants.sh index 0ef8b0b8e..f9a14498e 100755 --- a/contrib/release/check-invariants.sh +++ b/contrib/release/check-invariants.sh @@ -40,7 +40,8 @@ for crate in $crates; do recorded="$(lockfile_version "$REPO_ROOT/$lock" "$crate")" [ "$recorded" = "$version" ] || problem "$lock records $crate ${recorded:-}, expected $version" done - grep -qxF "## $version" "$REPO_ROOT/$crate/CHANGELOG.md" || problem "$crate/CHANGELOG.md has no ## $version section" + grep -qxF -e "## $version" -e "## [$version]" "$REPO_ROOT/$crate/CHANGELOG.md" || + problem "$crate/CHANGELOG.md has no ## $version section" done [ "$status" -eq 0 ] && echo "Release invariants hold for: $crates" diff --git a/contrib/release/extract-changelog.sh b/contrib/release/extract-changelog.sh index 8bd7549be..6baebc405 100755 --- a/contrib/release/extract-changelog.sh +++ b/contrib/release/extract-changelog.sh @@ -12,10 +12,11 @@ source "$(dirname "${BASH_SOURCE[0]}")/crates.sh" exit 1 } -# awk matches the heading exactly (a version has regex-special dots); sed -# drops leading blank lines and the command substitution drops trailing ones. -section="$(awk -v h="## $2" ' - $0 == h { inside = 1; next } +# awk matches the heading exactly (a version has regex-special dots), with +# or without the brackets the binding changelogs use; sed drops leading +# blank lines and the command substitution drops trailing ones. +section="$(awk -v h="## $2" -v b="## [$2]" ' + $0 == h || $0 == b { inside = 1; next } inside && /^## / { exit } inside ' "$REPO_ROOT/$1/CHANGELOG.md" | sed '/./,$!d')" diff --git a/contrib/release/tag.sh b/contrib/release/tag.sh index 52fc50ba8..5a2a26b2d 100755 --- a/contrib/release/tag.sh +++ b/contrib/release/tag.sh @@ -1,16 +1,15 @@ #!/usr/bin/env bash # # Sign the release tags for HEAD. For each release crate (and, with -# --bindings, each language package) whose manifest version has no tag yet, -# confirm the release invariants hold and HEAD is on payjoin/rust-payjoin's -# master, then create a signed annotated tag named - with the -# message "Release -". Prints the push command and never -# pushes. +# --bindings, the shared language-binding release) whose manifest version +# has no tag yet, confirm the release invariants hold and HEAD is on +# payjoin/rust-payjoin's master, then create a signed annotated tag named +# - with the message "Release -". Prints +# the push command and never pushes. # # usage: tag.sh [--dry-run] [--bindings] [--key ] [name...] -# name limit to these crates or packages: payjoin, payjoin-cli, -# payjoin-mailroom, or any payjoin-ffi/ with a -# contrib/release-version.sh +# name limit to payjoin, payjoin-cli, payjoin-mailroom, or payjoin-ffi +# (every language binding, from one tag) # # Run it in the release devShell, which pins jq, gpg and cargo: # nix develop .#release -c contrib/release/tag.sh [...] @@ -33,7 +32,7 @@ while [ "$#" -gt 0 ]; do shift ;; -h | --help) - sed -n '2,16p' "$0" + sed -n '2,15p' "$0" exit 0 ;; *) names+=("$1") ;; @@ -50,23 +49,6 @@ die() { # local ref can be stale. RELEASE_REPO="payjoin/rust-payjoin" -# Binding packages are the payjoin-ffi/ directories that ship a -# contrib/release-version.sh. It prints the version the next tag names, -# with +payjoin-X build metadata naming the wrapped payjoin version, and -# takes the payjoin version as its argument for packages whose manifest -# cannot hold that metadata. -binding_reader() { - printf '%s/payjoin-ffi/%s/contrib/release-version.sh' "$REPO_ROOT" "$1" -} -binding_names() { - local reader - for reader in "$REPO_ROOT"/payjoin-ffi/*/contrib/release-version.sh; do - [ -x "$reader" ] || continue - reader="${reader%/contrib/release-version.sh}" - printf '%s\n' "${reader##*/}" - done -} - [ -z "$(git status --porcelain --untracked-files=no)" ] || die "working tree has uncommitted changes; tags apply to HEAD" git fetch --quiet "https://github.com/$RELEASE_REPO.git" master || @@ -82,24 +64,21 @@ if [ "${#names[@]}" -gt 0 ]; then want=("${names[@]}") else read -r -a want <<<"$RELEASE_CRATES" - [ "$bindings" -eq 0 ] || mapfile -t -O "${#want[@]}" want < <(binding_names) + [ "$bindings" -eq 0 ] || want+=(payjoin-ffi) fi tags=() crates=() for name in "${want[@]}"; do - case " $RELEASE_CRATES " in - *" $name "*) - version="$(manifest_version "$name")" - prefix="$name" - ;; - *) - reader="$(binding_reader "$name")" - [ -x "$reader" ] || die "unknown name $name: not a release crate, and no $reader" - version="$("$reader" "$(manifest_version payjoin)")" || die "$reader failed" - prefix="payjoin-$name" - ;; - esac + if [[ " $RELEASE_CRATES " == *" $name "* ]]; then + version="$(manifest_version "$name")" + elif [ "$name" = payjoin-ffi ]; then + # One tag releases every language binding at this version. + version="$("$DIR/bindings-version.sh")" || die "binding versions disagree" + else + die "unknown name $name; use payjoin-ffi to release the language bindings" + fi + prefix="$name" [ -n "$version" ] || die "could not read a version for $name" tag="$prefix-$version" if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then @@ -114,16 +93,23 @@ for name in "${want[@]}"; do continue fi tags+=("$tag") - case " $RELEASE_CRATES " in - *" $name "*) crates+=("$name") ;; - esac + crates+=("$name") done [ "${#tags[@]}" -gt 0 ] || { echo "Nothing to tag: every manifest version is already tagged" exit 0 } -[ "${#crates[@]}" -eq 0 ] || "$DIR/check-invariants.sh" "${crates[@]}" +"$DIR/check-invariants.sh" "${crates[@]}" +# Dart consumers build the bindings from source against the wrapped payjoin +# on crates.io, so the bindings cannot ship before it. +case " ${crates[*]} " in + *" payjoin-ffi "*) + core="$(manifest_version payjoin)" + crate_published payjoin "$core" || + die "payjoin $core is not on crates.io; publish it before tagging payjoin-ffi" + ;; +esac sign=(--sign) [ -z "$key" ] || sign+=(--local-user "$key") @@ -145,7 +131,7 @@ remote="$(git remote -v | awk -v repo="$RELEASE_REPO" \ '$3 == "(push)" && $2 ~ ("[:/]" repo "(\\.git)?/?$") { print $1; exit }')" [ -n "$remote" ] || remote="https://github.com/$RELEASE_REPO.git" echo -echo "Push when ready; each tag starts its publish workflow and the release environment prompt:" +echo "Push when ready; each tag starts its release workflow and the release environment prompt:" printf ' git push %s' "$remote" printf ' %s' "${tags[@]}" echo diff --git a/flake.nix b/flake.nix index afee40989..15618d502 100644 --- a/flake.nix +++ b/flake.nix @@ -359,6 +359,7 @@ bzip2 curl jq + rsync ] ++ lib.optionals pkgs.stdenv.isLinux [ pkg-config diff --git a/payjoin-ffi/CHANGELOG.md b/payjoin-ffi/CHANGELOG.md index d762325c7..78ea889c6 100644 --- a/payjoin-ffi/CHANGELOG.md +++ b/payjoin-ffi/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.25.0] + +- Publish the Python, JavaScript, C#, and Dart packages from one signed + `payjoin-ffi` tag at this common version. Each package jumps from its + own sequence (Python and JavaScript 0.3.0, C# 0.2.0, Dart + 0.3.0+payjoin-1.2.0) with no API changes. +- Wraps payjoin 1.2.0, unchanged. + ## [0.24.0] ### Session Persister diff --git a/payjoin-ffi/Cargo.toml b/payjoin-ffi/Cargo.toml index c89310d3a..c4c8df83d 100644 --- a/payjoin-ffi/Cargo.toml +++ b/payjoin-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "payjoin-ffi" -version = "0.24.0" +version = "0.25.0" edition = "2024" license = "MIT OR Apache-2.0" exclude = ["tests"] diff --git a/payjoin-ffi/README.md b/payjoin-ffi/README.md index b56a17290..7b28aea4f 100644 --- a/payjoin-ffi/README.md +++ b/payjoin-ffi/README.md @@ -8,7 +8,9 @@ Our mission is to provide developers with cross-language libraries that seamless Each supported language is in its own directory. The Rust code in this project is in the `src` directory and is a wrapper around the Payjoin Dev Kit to expose its APIs uniformly using [UniFFI](https://github.com/mozilla/uniffi-rs) for each supported target language. -The directories below include instructions for using, building, and publishing the native language bindings supported by this project. +The directories below include instructions for using and building the language bindings. +See the [shared release instructions](RELEASING.md) for publishing all existing +packages with one signed payjoin-ffi tag. | Language | Platform | Repository | Published Package | | ---------- | --------------------- | ------------------------------------ | ----------------------------------------------- | diff --git a/payjoin-ffi/RELEASING.md b/payjoin-ffi/RELEASING.md new file mode 100644 index 000000000..d92b59007 --- /dev/null +++ b/payjoin-ffi/RELEASING.md @@ -0,0 +1,86 @@ +# Releasing the language bindings + +Python, JavaScript, C#, and Dart share the `payjoin-ffi` version and are +released together from one signed tag, +`payjoin-ffi-+payjoin-`, where `` is the wrapped +`payjoin` release. The first shared release is +`payjoin-ffi-0.25.0+payjoin-1.2.0`. Kotlin has no published package yet; +the tag only runs its checks. The `payjoin-ffi` crate itself is not +published to crates.io. + +## Versions + +`payjoin-ffi/Cargo.toml` holds the shared version. Each package manifest +repeats it in the form its registry accepts: + +| Manifest | Carries | +| ------------------------- | --------------------------------------------- | +| `python/pyproject.toml` | bare version, PEP 440 spelling | +| `javascript/package.json` | bare `version`, full version in `releaseTag` | +| `dart/pubspec.yaml` | full version | +| `csharp/Payjoin.csproj` | full version (NuGet drops the metadata again) | + +Pre-releases are `-rc.N` only; Python spells `0.26.0-rc.1` as `0.26.0rc1`, +and npm publishes pre-releases to the `next` dist-tag. Every release bumps +the version, including one that only changes the wrapped core, because +PyPI, npm, and NuGet ignore the build metadata when deciding whether a +version already exists. + +A bump PR changes all five manifests, both Cargo lockfiles +(`contrib/update-lock-files.sh`), `python/uv.lock`, +`javascript/package-lock.json`, and the changelogs. +`payjoin-ffi/CHANGELOG.md` is the canonical changelog and becomes the +GitHub release body. The language changelogs record only language-specific +changes and link to it; pub.dev displays Dart's. Check the manifests agree +with: + +```shell +nix develop .#release -c contrib/release/bindings-version.sh +``` + +## Registry configuration + +Before the first shared tag, point each trusted publisher at the new +workflow, keeping the existing package identities: PyPI, npm, and NuGet +use workflow `bindings-release.yml` and environment `release`; pub.dev +[automated publishing] uses tag pattern `payjoin-ffi-{{version}}` and +environment `release`. Keep required reviewers on the `release` +environment, and allow `payjoin-ffi-*` in the repository's tag rules. + +## Publishing + +1. Publish the wrapped `payjoin` release first. Dart consumers build the + bindings from source against it on crates.io. +2. Merge the bump PR and confirm every binding workflow is green on master. +3. Create the signed tag with a key in `contrib/release/keys/`: + + ```shell + nix develop .#release -c contrib/release/tag.sh payjoin-ffi + ``` + + The script checks that the manifests agree, that the lockfiles and + changelog match, that the core is published, and that HEAD is on + master. `tag.sh --bindings` selects the same tag alongside any pending + crate tags. Push the tag it prints. + +4. The `Release language bindings` run verifies the tag, builds and smoke + tests every language, then pauses four publish jobs on the `release` + environment. Approve the pending deployment once; it covers all four + jobs. Each registry is published with OIDC from the tested artifacts. +5. The run attaches the wheels, npm tarball, NuGet package, staged Dart + package, and one `SHA256SUMS` covering them all, including the native + libraries inside the NuGet package, to the GitHub release. Verify an + install from each registry and check provenance with + `gh attestation verify FILE -R payjoin/rust-payjoin`. Optionally sign + `SHA256SUMS` locally and upload `SHA256SUMS.asc`; no signing key reaches + a runner. + +## If a publish job fails + +Each registry has its own job, so use **Re-run failed jobs** on the same +run and approve the environment again. The re-run publishes from the same +tested artifacts and leaves the registries that already succeeded alone. +Never move a published tag or replace a published package; fix forward +with a new version. + +[automated publishing]: https://dart.dev/tools/pub/automated-publishing diff --git a/payjoin-ffi/csharp/Payjoin.csproj b/payjoin-ffi/csharp/Payjoin.csproj index 66af6a92f..2bcad112d 100644 --- a/payjoin-ffi/csharp/Payjoin.csproj +++ b/payjoin-ffi/csharp/Payjoin.csproj @@ -7,7 +7,7 @@ true true Payjoin - 0.2.0+payjoin-1.2.0 + 0.25.0+payjoin-1.2.0 Payjoin Payjoin Dev Kit Contributors C# bindings for payjoin-ffi, generated from rust-payjoin via UniFFI. diff --git a/payjoin-ffi/csharp/RELEASING.md b/payjoin-ffi/csharp/RELEASING.md index 436f1ffe1..6145337e2 100644 --- a/payjoin-ffi/csharp/RELEASING.md +++ b/payjoin-ffi/csharp/RELEASING.md @@ -6,26 +6,14 @@ package readme. ## Versioning -- The package version is set in `Payjoin.csproj` (``). -- It is the package's own semantic version, independent of the - `payjoin-ffi` crate version. The language bindings follow a - `{version}+payjoin-{version}` convention: the [SemVer] build metadata - names the wrapped payjoin core release, so `0.1.0+payjoin-1.0.0` - packages payjoin 1.0.0. NuGet accepts build metadata per its - [package versioning] guidance but ignores it for version comparison - and strips it from the `.nupkg` filename, so the package identity is - the bare version. -- Bump `MAJOR.MINOR.PATCH` for C# API changes, and update the build - metadata whenever the wrapped payjoin core version changes. -- A release that only changes the wrapped payjoin core version still - needs at least a patch bump: the package identity ignores the build - metadata, and nuget.org rejects republishing an identity that - already exists. -- Versions up to `0.24.0-preview.1` tracked the `payjoin-ffi` crate - version instead. They predate this scheme and are unlisted on - nuget.org so they do not resolve as the latest version. -- `Payjoin.csproj` is the only place the version is maintained: the CI smoke - test derives the version from the packed artifact. +All published bindings share the payjoin-ffi version, starting with +0.25.0. Follow the [shared release instructions](../RELEASING.md) to bump +versions and publish using one signed FFI tag. Retained build metadata +identifies the wrapped payjoin core, and registries that omit metadata +publish the bare FFI version. + +NuGet retains the historical `0.24.0-preview.1` release. The common +`0.25.0` release supersedes it as well as the independent C# sequence. ## Producing a release candidate @@ -68,7 +56,7 @@ Review before every publish to nuget.org. Grounded in the NuGet - [ ] Native assets are release-profile builds without `_test-utils` (the pack step's validation target enforces both; confirm it ran in CI). - [ ] The package is under nuget.org's 250 MB size limit. -- [ ] Package version in `Payjoin.csproj` carries the intended C# version +- [ ] Package version in `Payjoin.csproj` carries the common FFI version and its `+payjoin-{version}` build metadata matches the wrapped payjoin core release. @@ -108,40 +96,15 @@ Review before every publish to nuget.org. Grounded in the NuGet ## Publishing -CI is the publish path. A tag push builds, packs, smoke-tests, and pushes the -package to nuget.org via [trusted publishing] (OIDC) — no long-lived API key -is ever stored. The workflow is -[`.github/workflows/csharp.yml`](../../.github/workflows/csharp.yml) (jobs -`publish-nuget` and `github-release`). - -1. Work through the release readiness checklist above on the release commit in - `master`; confirm every `Build and Test CSharp` job is green. -2. Tag that commit with `contrib/release/tag.sh` and push the tag it - names. The tag is `payjoin-csharp-`, where `` is the - `Payjoin.csproj` `` exactly, build metadata included, as - [`contrib/release-version.sh`](contrib/release-version.sh) prints it. - NuGet strips the metadata from the `Payjoin..nupkg` filename, - so the publish job strips it from the tag as well before comparing. - tag.sh signs it with your key, which must be in `contrib/release/keys/` for - `verify-tag` to accept it, refuses a commit that is not on `master`, - and prints the push command. - - ```shell - nix develop .#release -c contrib/release/tag.sh csharp - ``` - - The tag reruns the full build/pack/smoke graph at the tagged commit, then - `publish-nuget` verifies the tag matches the packed - `Payjoin..nupkg`, attests build provenance, exchanges the GitHub - OIDC token for a short-lived nuget.org key via [`NuGet/login`], and pushes. - The job runs in the `release` environment: if it has required - reviewers, approve the paused run before anything reaches nuget.org. - -3. `github-release` attaches the `.nupkg` and a generated `SHA256SUMS` to the - tag's GitHub release. Optionally sign `SHA256SUMS` locally and upload - `SHA256SUMS.asc` — never place a GPG key on a runner. -4. Complete the post-publish verification section of the checklist, and verify - the attestation: - `gh attestation verify Payjoin..nupkg -R payjoin/rust-payjoin`. +Follow the [shared release procedure](../RELEASING.md). The +`payjoin-ffi-+payjoin-` tag reruns this language's +build and smoke checks before publishing through the existing `release` +environment with OIDC. The package version must match the common FFI +version, and its core metadata must match the declared core dependency. + +The GitHub release includes all language packages and +`SHA256SUMS`. Optional local signatures use +`SHA256SUMS.asc`. Verify installation from the registry and +check provenance with `gh attestation verify FILE -R payjoin/rust-payjoin`. [trusted publishing]: https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing diff --git a/payjoin-ffi/csharp/contrib/release-version.sh b/payjoin-ffi/csharp/contrib/release-version.sh deleted file mode 100755 index fc2b1f6e5..000000000 --- a/payjoin-ffi/csharp/contrib/release-version.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# -# Print the version the next payjoin-csharp release tag names: the -# of Payjoin.csproj. -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")/.." -version="$(sed -n 's/.*\(.*\)<\/Version>.*/\1/p' Payjoin.csproj | head -1)" -[ -n "$version" ] || exit 1 -printf '%s\n' "$version" diff --git a/payjoin-ffi/dart/.pubignore b/payjoin-ffi/dart/.pubignore index c8c635895..d0ac83c8c 100644 --- a/payjoin-ffi/dart/.pubignore +++ b/payjoin-ffi/dart/.pubignore @@ -16,6 +16,7 @@ scripts/ # Cargo build output native/target/ +native/Cargo.lock # Local-only test helper re-export lib/test_utils.dart diff --git a/payjoin-ffi/dart/CHANGELOG.md b/payjoin-ffi/dart/CHANGELOG.md index 6c209115a..3179933dc 100644 --- a/payjoin-ffi/dart/CHANGELOG.md +++ b/payjoin-ffi/dart/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.25.0+payjoin-1.2.0] + +- Version aligned with payjoin-ffi 0.25.0. Releases now share one tag + and the [payjoin-ffi changelog](../CHANGELOG.md). No API changes since + 0.3.0+payjoin-1.2.0. + ## [0.3.0+payjoin-1.2.0] - Bindings for payjoin-1.2.0, which adds the non-blocking receive diff --git a/payjoin-ffi/dart/CONTRIBUTING.md b/payjoin-ffi/dart/CONTRIBUTING.md index e70533173..b018b92ff 100644 --- a/payjoin-ffi/dart/CONTRIBUTING.md +++ b/payjoin-ffi/dart/CONTRIBUTING.md @@ -50,34 +50,23 @@ the `Build and Test Dart` workflow regenerates the production bindings and validates the archive with a publish dry run ([`contrib/prepare-publish.sh`](contrib/prepare-publish.sh)). -1. Point the `payjoin-ffi` dependency in `native/Cargo.toml` at the commit - tagged for the `payjoin` release being wrapped. Consumers build from that - revision. `.cargo/config.toml` redirects it to the local workspace for - development only, and `.pubignore` withholds that file from the archive. -2. Set the version in `pubspec.yaml` and describe the consumer-visible - changes under a matching heading in `CHANGELOG.md`. -3. Confirm every `Build and Test Dart` job is green on the release commit - in `master`. -4. Tag that commit with `contrib/release/tag.sh` and push the tag it - names. The tag is `payjoin-dart-`, where `` is the - `pubspec.yaml` version exactly (including the `+` build metadata), as - [`contrib/release-version.sh`](contrib/release-version.sh) prints it. - tag.sh signs it with your key, which must be in `contrib/release/keys/` for - `verify-tag` to accept it, refuses a commit that is not on `master`, - and prints the push command. +1. The preparation script stages a separate package and pins its Rust + dependency to the checkout used to generate production bindings. The + development pin in `native/Cargo.toml` is replaced only in that staging + directory. Cargo overlays and local lockfiles are excluded. Both the + publish dry run and publication use the staged package. For local checks: ```shell - nix develop .#release -c contrib/release/tag.sh dart + nix develop .#dart -c ./payjoin-ffi/dart/contrib/prepare-publish.sh /tmp/payjoin-pub ``` - The tag reruns the tests and the archive verification at the tagged - commit, then `publish-pub` verifies the tag matches `pubspec.yaml`, - regenerates the production bindings, and publishes through pub.dev - [automated publishing] (OIDC), so no long-lived credential is stored - anywhere. The job runs in the `release` environment: approve the paused - run before anything reaches the registry. - -5. Verify the [pub.dev listing](https://pub.dev/packages/payjoin) shows the - new version and its changelog. - -[automated publishing]: https://dart.dev/tools/pub/automated-publishing +2. Follow the [shared bindings release instructions](../RELEASING.md). + `pubspec.yaml` carries the common FFI version and the wrapped core + metadata, for example `0.25.0+payjoin-1.2.0`. One signed + `payjoin-ffi-+payjoin-` tag starts all publishers. +3. Configure pub.dev automated publishing with tag pattern + `payjoin-ffi-{{version}}` and environment `release` before the first + shared release. Approve the release environment when prompted. +4. Verify the [pub.dev listing](https://pub.dev/packages/payjoin) shows the + new version and changelog. The staged package's native Rust revision + must equal the shared tag's commit. diff --git a/payjoin-ffi/dart/contrib/prepare-publish.sh b/payjoin-ffi/dart/contrib/prepare-publish.sh index 1fd533e18..5a57e6706 100755 --- a/payjoin-ffi/dart/contrib/prepare-publish.sh +++ b/payjoin-ffi/dart/contrib/prepare-publish.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash set -euo pipefail +[ "$#" -eq 1 ] || { + echo "usage: prepare-publish.sh " >&2 + exit 1 +} +# Resolve before changing directories; the destination must not exist. +stage="$(realpath -m "$1")" # Prepare the package for publishing to pub.dev and verify the archive with # a dry run. The archive ships Dart source plus the native/ wrapper crate; @@ -20,10 +26,17 @@ cd "$REPO_ROOT/payjoin-ffi/dart" echo "==> Generating production FFI bindings..." PAYJOIN_FFI_FEATURES="" bash ./scripts/generate_bindings.sh -# A Cargo.lock resolved against the .cargo/config.toml path overlay would -# hand consumers a dependency graph they cannot reproduce. -echo "==> Cleaning nested Cargo.lock..." -rm -f native/Cargo.lock +revision="$(git rev-parse HEAD)" +mkdir "$stage" +# Include generated bindings while excluding development Cargo overlays and +# lockfiles, then pin the native crate to the checkout that generated them. +rsync -a --exclude=.git --exclude-from=.pubignore ./ "$stage/" +sed -i "s/rev = \"[0-9a-f]*\"/rev = \"$revision\"/" "$stage/native/Cargo.toml" +grep -Fq "rev = \"$revision\"" "$stage/native/Cargo.toml" || { + echo "Could not pin the staged native crate to $revision" >&2 + exit 1 +} +cd "$stage" echo "==> Verifying the publish archive..." # The dry run exits 65 whenever validation reports anything, and one @@ -39,6 +52,10 @@ if [[ $status -ne 0 && $status -ne 65 ]]; then exit "$status" fi unexpected="$(grep '^\* ' <<<"$report" | grep -v "^\* \`dart analyze\` found" || true)" +# Skipping ahead of the last published version is a release decision made +# when the shared binding version is bumped, so pub's reminder of the +# previous version is not a finding. +unexpected="$(grep -v '^\* The previous version is ' <<<"$unexpected" || true)" if [[ -n $unexpected ]]; then echo "Unexpected validation findings; fix them before publishing:" >&2 printf '%s\n' "$unexpected" >&2 diff --git a/payjoin-ffi/dart/contrib/publish.sh b/payjoin-ffi/dart/contrib/publish.sh index e6917d551..3a6f7c877 100755 --- a/payjoin-ffi/dart/contrib/publish.sh +++ b/payjoin-ffi/dart/contrib/publish.sh @@ -1,5 +1,16 @@ #!/usr/bin/env bash set -euo pipefail +[ "$#" -eq 1 ] || { + echo "usage: publish.sh " >&2 + exit 1 +} +revision="$(git -C "$(dirname "${BASH_SOURCE[0]}")/.." rev-parse HEAD)" +cd "$1" +# Publish only what prepare-publish.sh staged for this checkout. +grep -Fq "rev = \"$revision\"" native/Cargo.toml || { + echo "Staged native/Cargo.toml must pin the release checkout $revision" >&2 + exit 1 +} # Publish the package to pub.dev via automated publishing (OIDC). # `dart pub publish` has no non-interactive authentication mode: without a @@ -9,8 +20,7 @@ set -euo pipefail # short-lived token that pub.dev verifies against the repository, tag # pattern, and environment configured on the package's admin page. This is # the same exchange the dart-lang/setup-dart action performs; we install -# Dart through nix, so it has to happen here instead. Run -# prepare-publish.sh first to generate the bindings that get packed. +# Dart through nix, so it has to happen here instead. : "${ACTIONS_ID_TOKEN_REQUEST_URL:?must run in a GitHub Actions job with id-token: write}" : "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:?must run in a GitHub Actions job with id-token: write}" @@ -24,5 +34,4 @@ PUB_TOKEN="$( export PUB_TOKEN dart pub token add https://pub.dev --env-var PUB_TOKEN -cd "$(dirname "$0")/.." dart pub publish --force diff --git a/payjoin-ffi/dart/contrib/release-version.sh b/payjoin-ffi/dart/contrib/release-version.sh deleted file mode 100755 index 74fa4e79f..000000000 --- a/payjoin-ffi/dart/contrib/release-version.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# -# Print the version the next payjoin-dart release tag names: the -# `version` of pubspec.yaml. -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")/.." -version="$(sed -n 's/^version: *\(.*\)$/\1/p' pubspec.yaml | head -1)" -[ -n "$version" ] || exit 1 -printf '%s\n' "$version" diff --git a/payjoin-ffi/dart/pubspec.yaml b/payjoin-ffi/dart/pubspec.yaml index e84b56a02..037557182 100644 --- a/payjoin-ffi/dart/pubspec.yaml +++ b/payjoin-ffi/dart/pubspec.yaml @@ -1,6 +1,6 @@ name: payjoin description: Dart bindings for payjoin -version: 0.3.0+payjoin-1.2.0 +version: 0.25.0+payjoin-1.2.0 homepage: https://github.com/payjoin/rust-payjoin repository: https://github.com/payjoin/rust-payjoin diff --git a/payjoin-ffi/javascript/CHANGELOG.md b/payjoin-ffi/javascript/CHANGELOG.md index 9631fee1c..a643596cd 100644 --- a/payjoin-ffi/javascript/CHANGELOG.md +++ b/payjoin-ffi/javascript/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.25.0] + +- Version aligned with payjoin-ffi 0.25.0. Releases now share one tag + and the [payjoin-ffi changelog](../CHANGELOG.md). No API changes since + 0.3.0. + ## [0.3.0] - Bindings for payjoin-1.2.0, which adds the non-blocking receive diff --git a/payjoin-ffi/javascript/RELEASING.md b/payjoin-ffi/javascript/RELEASING.md index 67059c4c5..d06aa5ab1 100644 --- a/payjoin-ffi/javascript/RELEASING.md +++ b/payjoin-ffi/javascript/RELEASING.md @@ -1,62 +1,27 @@ # Releasing the payjoin npm package -Maintainer documentation for publishing the `payjoin` package to -[npmjs.com](https://www.npmjs.com/package/payjoin). Consumer documentation -lives in [`README.md`](README.md). +Follow the [shared bindings release instructions](../RELEASING.md) for +version bumps, registry configuration, and tagging. One +`payjoin-ffi-+payjoin-` tag releases every +binding. ## Versioning -- The package version is set in `package.json`. -- It is the package's own semantic version, independent of the - `payjoin-ffi` crate version. The language bindings follow a - `{version}+payjoin-{version}` convention where the build metadata - names the wrapped payjoin core release, but npm strips build metadata - from published versions, so `package.json` carries the bare version - and records the full one in a `releaseTag` field for traceability. - The release tag carries the full version too. Keep `releaseTag` in - sync when bumping the version. -- A release that only changes the wrapped payjoin core version still - needs at least a patch bump: npm sees only the bare version and - rejects republishing one that already exists. -- `package.json` is the only place the version is maintained: the publish - job derives the version from the packed tarball and refuses to publish - if it does not match the pushed tag. +Starting with 0.25.0, `package.json` uses the common FFI version. Its +`releaseTag` field appends the wrapped payjoin core as build metadata. +Keep both version entries in `package-lock.json` synchronized. npm +publishes the bare version, so changing only metadata requires another +common version bump. -## Publishing +## Package checks -CI is the publish path. On every pull request touching `payjoin-ffi/**`, -the `Build and Test JavaScript` workflow builds the wasm package, packs the -tarball with [`contrib/pack.sh`](contrib/pack.sh), and smoke-installs it on -Linux and macOS. The tarball ships only `dist/` (wasm + compiled -TypeScript), which is platform-independent. +The JavaScript workflow builds production wasm and compiled TypeScript, +packs the tarball with `contrib/pack.sh`, and smoke tests installation on +Linux and macOS. The package ships only `dist/` and is platform independent. +The publishing job compares the tag with the packed artifact, attests it, +and publishes through npm trusted publishing in the `release` environment. -1. Confirm every `Build and Test JavaScript` job is green on the release - commit in `master`. -2. Tag that commit with `contrib/release/tag.sh` and push the tag it - names. The tag is `payjoin-javascript-`, where `` is - the `package.json` `releaseTag` value exactly, as - [`contrib/release-version.sh`](contrib/release-version.sh) prints it; - the publish job strips the build metadata before comparing the tag - against the packed tarball. tag.sh signs it with your key, which must be in `contrib/release/keys/` for - `verify-tag` to accept it, refuses a commit that is not on `master`, - and prints the push command. - - ```shell - nix develop .#release -c contrib/release/tag.sh javascript - ``` - - The tag reruns the full build/pack/smoke graph at the tagged commit, - then `publish-npm` verifies the tag matches the packed tarball, attests - build provenance, and publishes through npm - [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC), - so no long-lived token is stored anywhere. The job runs in the `release` - environment: approve the paused run before anything reaches the - registry. - -3. `github-release` attaches the tarball and a generated `SHA256SUMS` to - the tag's GitHub release. Optionally sign `SHA256SUMS` locally and - upload `SHA256SUMS.asc`. -4. Verify the publication: the npmjs.com listing shows the new version - with a provenance badge, and - `gh attestation verify payjoin-.tgz -R payjoin/rust-payjoin` - passes against the release asset. +The shared GitHub release includes the tarball and +`SHA256SUMS`. An optional local signature is named +`SHA256SUMS.asc`. Verify the registry version, its provenance +badge, and `gh attestation verify FILE -R payjoin/rust-payjoin`. diff --git a/payjoin-ffi/javascript/contrib/release-version.sh b/payjoin-ffi/javascript/contrib/release-version.sh deleted file mode 100755 index 94c86f2e4..000000000 --- a/payjoin-ffi/javascript/contrib/release-version.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# -# Print the version the next payjoin-javascript release tag names: the -# `releaseTag` field of package.json. npm rejects build metadata in -# `version`, so the full version lives in `releaseTag`. -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")/.." -jq -er '.releaseTag' package.json diff --git a/payjoin-ffi/javascript/package-lock.json b/payjoin-ffi/javascript/package-lock.json index 77bf6f1c1..38c75753f 100644 --- a/payjoin-ffi/javascript/package-lock.json +++ b/payjoin-ffi/javascript/package-lock.json @@ -1,12 +1,12 @@ { "name": "payjoin", - "version": "0.3.0", + "version": "0.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "payjoin", - "version": "0.3.0", + "version": "0.25.0", "dependencies": { "@ubjs/core": "^0.31.0-3", "uniffi-bindgen-react-native": "github:spacebear21/uniffi-bindgen-react-native#d10f04d83299" diff --git a/payjoin-ffi/javascript/package.json b/payjoin-ffi/javascript/package.json index 6f76422b6..2b6e40e9a 100644 --- a/payjoin-ffi/javascript/package.json +++ b/payjoin-ffi/javascript/package.json @@ -1,7 +1,7 @@ { "name": "payjoin", - "version": "0.3.0", - "releaseTag": "0.3.0+payjoin-1.2.0", + "version": "0.25.0", + "releaseTag": "0.25.0+payjoin-1.2.0", "description": "JavaScript/WASM bindings for rust-payjoin (EXPERIMENTAL)", "scripts": { "ubrn:nodejs": "ubrn build web --release --and-generate --config ubrn.nodejs.config.yaml", diff --git a/payjoin-ffi/python/CHANGELOG.md b/payjoin-ffi/python/CHANGELOG.md index d14b0b563..a3423e325 100644 --- a/payjoin-ffi/python/CHANGELOG.md +++ b/payjoin-ffi/python/CHANGELOG.md @@ -1,3 +1,9 @@ +## [0.25.0] + +- Version aligned with payjoin-ffi 0.25.0. Releases now share one tag + and the [payjoin-ffi changelog](../CHANGELOG.md). No API changes since + 0.3.0. + ## [0.3.0] - Bindings for payjoin-1.2.0, which adds the non-blocking receive diff --git a/payjoin-ffi/python/RELEASING.md b/payjoin-ffi/python/RELEASING.md index c172aaa47..17412dee9 100644 --- a/payjoin-ffi/python/RELEASING.md +++ b/payjoin-ffi/python/RELEASING.md @@ -6,21 +6,11 @@ Maintainer documentation for publishing the `payjoin` package to ## Versioning -- The package version is set in `pyproject.toml` (`project.version`). -- It is the package's own semantic version, independent of the - `payjoin-ffi` crate version. The language bindings follow a - `{version}+payjoin-{version}` convention where the build metadata - names the wrapped payjoin core release, but PyPI rejects PEP 440 - local version labels (the `+` part), so the package publishes the - bare version. The release tag carries the full version, and the - wrapped payjoin core version is also recorded in - [`CHANGELOG.md`](CHANGELOG.md). -- A release that only changes the wrapped payjoin core version still - needs at least a patch bump: PyPI sees only the bare version and - rejects re-uploading one that already exists. -- `pyproject.toml` is the only place the version is maintained: the - publish job derives the version from the built wheels and refuses to - publish if it does not match the pushed tag. +All published bindings share the payjoin-ffi version, starting with +0.25.0. Follow the [shared release instructions](../RELEASING.md) to bump +versions and publish using one signed FFI tag. Retained build metadata +identifies the wrapped payjoin core, and registries that omit metadata +publish the bare FFI version. ## Producing the wheels @@ -41,34 +31,13 @@ CPython satisfying `requires-python` can install them. ## Publishing -1. Confirm every `Build and Test Python` job is green on the release - commit in `master`, including the per-platform smoke tests. -2. Tag that commit with `contrib/release/tag.sh` and push the tag it - names. The tag is `payjoin-python-`, where `` is the - `pyproject.toml` version plus `+payjoin-` build metadata - naming the wrapped payjoin core release, as - [`contrib/release-version.sh`](contrib/release-version.sh) prints it; - the publish job strips the metadata before comparing the tag against - the built wheels. tag.sh signs it with your key, which must be in `contrib/release/keys/` for - `verify-tag` to accept it, refuses a commit that is not on `master`, - and prints the push command. +Follow the [shared release procedure](../RELEASING.md). The +`payjoin-ffi-+payjoin-` tag reruns this language's +build and smoke checks before publishing through the existing `release` +environment with OIDC. The package version must match the common FFI +version, and its core metadata must match the declared core dependency. - ```shell - nix develop .#release -c contrib/release/tag.sh python - ``` - - The tag reruns the full build/wheel/smoke graph at the tagged commit, - then `publish-pypi` verifies the tag matches the built wheels, attests - build provenance, and uploads through PyPI - [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) - with PEP 740 attestations, so no long-lived token is stored anywhere. - The job runs in the `release` environment: approve the paused run before - anything reaches the registry. - -3. `github-release` attaches the wheels and a generated `SHA256SUMS` to - the tag's GitHub release. Optionally sign `SHA256SUMS` locally and - upload `SHA256SUMS.asc`. -4. Verify the publication: the PyPI listing shows the new version, - `pip install payjoin==` resolves on a supported platform, and - `gh attestation verify -R payjoin/rust-payjoin` passes against - a release asset. +The GitHub release includes all language packages and +`SHA256SUMS`. Optional local signatures use +`SHA256SUMS.asc`. Verify installation from the registry and +check provenance with `gh attestation verify FILE -R payjoin/rust-payjoin`. diff --git a/payjoin-ffi/python/contrib/release-version.sh b/payjoin-ffi/python/contrib/release-version.sh deleted file mode 100755 index 9af3c7796..000000000 --- a/payjoin-ffi/python/contrib/release-version.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -# -# Print the version the next payjoin-python release tag names: -# +payjoin-. PyPI rejects local -# versions, so pyproject.toml holds only the bare version and the build -# metadata is added here; the publish workflow strips it again. -# -# usage: release-version.sh -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")/.." -[ -n "${1:-}" ] || exit 1 -version="$(awk '/^\[/ { in_project = ($0 == "[project]") } - in_project && /^version *=/ { gsub(/^version *= *"|"$/, ""); print; exit }' pyproject.toml)" -[ -n "$version" ] || exit 1 -printf '%s+payjoin-%s\n' "$version" "$1" diff --git a/payjoin-ffi/python/pyproject.toml b/payjoin-ffi/python/pyproject.toml index 33d8f00bc..4134342ab 100644 --- a/payjoin-ffi/python/pyproject.toml +++ b/payjoin-ffi/python/pyproject.toml @@ -8,7 +8,7 @@ description = "The Python language bindings for the Payjoin Dev Kit" readme = "README.md" requires-python = ">=3.10" license = "MIT" -version = "0.3.0" +version = "0.25.0" dependencies = ["httpx>=0.28.1,<1.0"] [tool.setuptools] diff --git a/payjoin-ffi/python/uv.lock b/payjoin-ffi/python/uv.lock index bbf696091..f644d2e19 100644 --- a/payjoin-ffi/python/uv.lock +++ b/payjoin-ffi/python/uv.lock @@ -82,7 +82,7 @@ wheels = [ [[package]] name = "payjoin" -version = "0.3.0" +version = "0.25.0" source = { editable = "." } dependencies = [ { name = "httpx" },