diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a9eca440c..ed247213b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -109,6 +109,12 @@ fi echo "✓ Pre-commit hook passed" ``` +### Changelog + +Each release crate (`payjoin`, `payjoin-cli`, `payjoin-mailroom`) keeps a `CHANGELOG.md` with an `## Unreleased` section at the top. If your pull request changes what a user of that crate sees, add a line there in the same commit. Write it for the person reading the release notes, not for the reviewer. The release PR renames the heading to the version number and nothing else. + +If the change has no user-visible effect (refactoring, tests, CI, comments), say so in the pull request description and a maintainer will apply the `changelog: none` label. CI fails a pull request that touches a release crate's `src/` without either a changelog line or that label. + ## AI Assistance Notice > [!IMPORTANT] diff --git a/.github/ISSUE_TEMPLATE/minor-release.md b/.github/ISSUE_TEMPLATE/minor-release.md index 206e6efe0..d77f7000f 100644 --- a/.github/ISSUE_TEMPLATE/minor-release.md +++ b/.github/ISSUE_TEMPLATE/minor-release.md @@ -1,65 +1,66 @@ --- name: Minor Release -about: Checklist for releasing a new minor version bump -title: Release MAJOR.MINOR+1.0 +about: Checklist for releasing a new minor version of a release crate +title: Release CRATE-MAJOR.MINOR+1.0 labels: "" assignees: "" --- -## Create a new minor release +## Release CRATE MAJOR.MINOR+1.0 -### Summary - - - -### Commit +Release-managed crates are `payjoin`, `payjoin-cli` and `payjoin-mailroom`. Versions follow +[Semantic Versioning]. Tags are `-`. The changelog for this release is the +`## Unreleased` section of `CRATE/CHANGELOG.md`; it was written by the pull requests that +landed since the last release, so there is nothing to compile here. - - -### Changelog +### Summary - + ### Checklist -Release numbering must follow [Semantic Versioning]. These steps assume the current `master` -branch **development** version is _MAJOR.MINOR.0_. Release-managed crates are `payjoin`, -`payjoin-cli`, and `payjoin-mailroom`; tags use the `-` scheme. - -#### On the day of the feature freeze - -Change the `master` branch to the next MINOR+1 version: - -- [ ] Switch to the `master` branch. -- [ ] Create a new PR branch called `bump-CRATE-MAJOR-MINOR+1`, eg. `bump-CRATE-0-22`. -- [ ] Bump the `bump-CRATE-MAJOR-MINOR+1` branch to the next development MINOR+1 version. - - Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0` for the crate being released, - and update every workspace member's version requirement on it to match. - - Run `contrib/update-lock-files.sh` to apply upgrades to the Cargo lock files. - - Update the crate's `CHANGELOG.md` file, adding a `## MAJOR.MINOR+1.0` section that - summarizes the PRs merged since the last release tag. - - The commit message should be "Bump CRATE version to MAJOR.MINOR+1.0". -- [ ] Create PR for the `bump-CRATE-MAJOR-MINOR+1` branch to `master`. - - Title PR "Bump CRATE version to MAJOR.MINOR+1.0". -- [ ] Merge the `bump-CRATE-MAJOR-MINOR+1` branch to `master`. - -If any issues need to be fixed before the _MAJOR.MINOR+1.0_ version is released: - -- [ ] Merge fix PRs to the `master` branch. -- [ ] Git cherry-pick fix commits to the `bump-CRATE-MAJOR.MINOR+1` branch. -- [ ] Verify fixes in `bump-CRATE-MAJOR.MINOR+1` branch. - -#### On the day of the release - -- [ ] Create a signed annotated tag on the `HEAD` commit in the `master` branch. - - The tag name should be `CRATE-MAJOR.MINOR+1.0`, eg. `payjoin-1.0.0`. - - The first line of the tag message should be "Release CRATE-MAJOR.MINOR+1.0". - - In the body of the tag message put a copy of the **Summary** and **Changelog** for the release. - - Sign with a key committed under `contrib/release/keys/`, using the explicit `--sign` flag. -- [ ] Push the new tag to the `payjoin/rust-payjoin` repo. -- [ ] Approve the `release` environment when it requests a reviewer. -- [ ] Announce the release, using the **Summary**, on Discord, Twitter, Nostr, and stacker.news. -- [ ] Celebrate 🎉 +#### Bump + +- [ ] Branch `bump-CRATE-MAJOR-MINOR+1` from `master`. +- [ ] Run `contrib/release/bump.sh CRATE MAJOR.MINOR+1.0`. It sets the version, rewrites + every workspace member's requirement on the crate, renames `## Unreleased` to + `## MAJOR.MINOR+1.0` with a fresh empty `## Unreleased` above it, regenerates both + lock files and runs check-invariants. It needs jq, and the lock file step calls + `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. +- [ ] 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, + for `payjoin`, cargo-semver-checks. +- [ ] Merge. + +If a fix must land before the tag, merge it to `master` as usual; it carries its own +changelog line. The bump PR is not edited for it. + +#### Tag and publish + +- [ ] Check out the merged `master` commit. Run + `nix develop .#release -c contrib/release/tag.sh CRATE`. It checks the tree is clean + and on payjoin/rust-payjoin's `master` (fetched by URL, so any remote layout works), + runs check-invariants, and creates the signed tag `CRATE-MAJOR.MINOR+1.0` with a key + from `contrib/release/keys/`. +- [ ] Push the tag with the command the script prints. +- [ ] Approve the `release` environment when the workflow asks. Publication, the GitHub + release and the crates.io/docs.rs verification run from there. + +#### 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. +- [ ] 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. + +#### Announce + +- [ ] Discord, Twitter, Nostr, stacker.news, using the Summary. [Semantic Versioning]: https://semver.org/ -["keep a changelog"]: https://keepachangelog.com/en/1.0.0/ diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml new file mode 100644 index 000000000..7e7066d72 --- /dev/null +++ b/.github/workflows/changelog-check.yml @@ -0,0 +1,41 @@ +# .github/workflows/changelog-check.yml +# +# A pull request that changes a release crate's source must add a line to +# that crate's CHANGELOG.md under "## Unreleased", or carry the +# "changelog: none" label. The bump PR then only renames the heading, so a +# merge under an open bump PR never requires editing it. +name: Changelog + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + +jobs: + changelog: + name: Changelog entry + if: "${{ !contains(github.event.pull_request.labels.*.name, 'changelog: none') }}" + runs-on: ubuntu-26.04 + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Require a changelog line for each changed release crate + env: + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + source contrib/release/crates.sh + changed="$(git diff --name-only "$BASE" "$HEAD")" + status=0 + for crate in $RELEASE_CRATES; do + grep -q "^$crate/src/" <<<"$changed" || continue + if ! grep -q "^$crate/CHANGELOG.md$" <<<"$changed"; then + echo "::error::$crate/src changed but $crate/CHANGELOG.md did not; add a line under '## Unreleased', or say in the PR description that there is no user-visible change so a maintainer can label it 'changelog: none'" + status=1 + fi + done + exit "$status" diff --git a/contrib/release/bump.sh b/contrib/release/bump.sh new file mode 100755 index 000000000..f6f05d31d --- /dev/null +++ b/contrib/release/bump.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# +# Prepare a release crate's bump commit. Sets the crate's version, rewrites +# every workspace member's version requirement on it, renames the +# "## Unreleased" changelog section to the new version and opens a fresh +# empty one above it, regenerates both lock files, then runs +# check-invariants. Leaves the changes uncommitted for review. +# +# usage: bump.sh [--no-lock] +# --no-lock skip contrib/update-lock-files.sh (then check-invariants +# reports the stale lock files, as it should) +# +# Needs jq, and without --no-lock a rustup nightly toolchain, which the +# lock file script calls as `cargo +nightly`. +set -euo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=contrib/release/crates.sh +source "$DIR/crates.sh" +cd "$REPO_ROOT" + +lock=1 +if [ "${1:-}" = "--no-lock" ]; then + lock=0 + shift +fi +[ "$#" -eq 2 ] || { + sed -n '2,14p' "$0" >&2 + exit 1 +} +crate="$1" +new="$2" +die() { + echo "bump: $*" >&2 + exit 1 +} + +case " $RELEASE_CRATES " in + *" $crate "*) ;; + *) die "$crate is not a release crate ($RELEASE_CRATES)" ;; +esac +[[ $new =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]] || die "$new is not a semver version" +[ -z "$(git status --porcelain --untracked-files=no)" ] || die "commit or discard your changes first" + +RELEASE_METADATA="$(cargo_metadata)" +old="$(manifest_version "$crate")" +[ "$old" != "$new" ] || die "$crate is already $new" +echo "Bumping $crate $old -> $new" + +# The crate's own version: the first line-anchored `version =` in its manifest. +sed -i "0,/^version = \"$old\"/s//version = \"$new\"/" "$crate/Cargo.toml" +[ "$(sed -n 's/^version = "\(.*\)"/\1/p' "$crate/Cargo.toml" | head -1)" = "$new" ] || + die "could not set version in $crate/Cargo.toml" + +# Every workspace member that requires it by version. Members use the +# single-line table form `crate = { version = "x", ... }`. +while IFS= read -r manifest; do + [ -f "$manifest" ] || continue + if grep -q "^$crate = { version = \"$old\"" "$manifest"; then + sed -i "s/^\($crate = { version = \"\)$old\"/\1$new\"/" "$manifest" + echo " $manifest: requirement on $crate -> $new" + fi +done < <(cargo_metadata | jq -r '.packages[].manifest_path') + +changelog="$crate/CHANGELOG.md" +grep -qx '## Unreleased' "$changelog" || + die "$changelog has no '## Unreleased' section; add the lines for this release under one first" +sed -i "0,/^## Unreleased$/s//## Unreleased\n\n## $new/" "$changelog" +echo " $changelog: '## Unreleased' -> '## $new', fresh Unreleased section added" + +if [ "$lock" -eq 1 ]; then + echo "Regenerating lock files (contrib/update-lock-files.sh)" + ./contrib/update-lock-files.sh +fi + +"$DIR/check-invariants.sh" "$crate" +echo +echo "Review the '## $new' section of $changelog, then commit as:" +echo " git commit -am 'Bump $crate version to $new'" diff --git a/contrib/release/tag.sh b/contrib/release/tag.sh new file mode 100755 index 000000000..52fc50ba8 --- /dev/null +++ b/contrib/release/tag.sh @@ -0,0 +1,151 @@ +#!/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. +# +# 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 +# +# Run it in the release devShell, which pins jq, gpg and cargo: +# nix develop .#release -c contrib/release/tag.sh [...] +set -euo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=contrib/release/crates.sh +source "$DIR/crates.sh" +cd "$REPO_ROOT" + +dry_run=0 +bindings=0 +key="" +names=() +while [ "$#" -gt 0 ]; do + case "$1" in + --dry-run) dry_run=1 ;; + --bindings) bindings=1 ;; + --key) + key="$2" + shift + ;; + -h | --help) + sed -n '2,16p' "$0" + exit 0 + ;; + *) names+=("$1") ;; + esac + shift +done +die() { + echo "tag: $*" >&2 + exit 1 +} + +# Master is fetched from the canonical repository by URL rather than read +# from a remote-tracking ref: a maintainer's origin is often a fork, and any +# 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 || + die "could not fetch master from github.com/$RELEASE_REPO" +master="$(git rev-parse FETCH_HEAD)" +git merge-base --is-ancestor HEAD "$master" || + die "HEAD is not on $RELEASE_REPO master, so verify-tag-hygiene would reject the tag; check out the merged commit" + +RELEASE_METADATA="$(cargo_metadata)" +explicit=0 +if [ "${#names[@]}" -gt 0 ]; then + explicit=1 + want=("${names[@]}") +else + read -r -a want <<<"$RELEASE_CRATES" + [ "$bindings" -eq 0 ] || mapfile -t -O "${#want[@]}" want < <(binding_names) +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 + [ -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 + at="$(git rev-list -n1 "$tag")" + if [ "$at" = "$(git rev-parse HEAD)" ]; then + echo "$tag already tagged at HEAD, skipping" + elif [ "$explicit" -eq 1 ]; then + die "$tag already exists at ${at:0:9}, not HEAD; bump the version first" + else + echo "$name is at $version, released at ${at:0:9}; not bumped, skipping" + fi + continue + fi + tags+=("$tag") + case " $RELEASE_CRATES " in + *" $name "*) crates+=("$name") ;; + esac +done +[ "${#tags[@]}" -gt 0 ] || { + echo "Nothing to tag: every manifest version is already tagged" + exit 0 +} + +[ "${#crates[@]}" -eq 0 ] || "$DIR/check-invariants.sh" "${crates[@]}" + +sign=(--sign) +[ -z "$key" ] || sign+=(--local-user "$key") +for tag in "${tags[@]}"; do + if [ "$dry_run" -eq 1 ]; then + echo "would run: git tag ${sign[*]} --annotate $tag -m 'Release $tag' HEAD" + continue + fi + git tag "${sign[@]}" --annotate "$tag" -m "Release $tag" HEAD + RELEASE_MASTER="$master" "$DIR/verify-tag-hygiene.sh" "$tag" >/dev/null || + die "$tag failed hygiene after creation; delete it with: git tag -d $tag" + echo "created $tag" +done + +[ "$dry_run" -eq 0 ] || exit 0 +# Name the remote that points at the canonical repository, whatever it is +# called locally, or fall back to its URL. +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:" +printf ' git push %s' "$remote" +printf ' %s' "${tags[@]}" +echo diff --git a/contrib/release/verify-tag-hygiene.sh b/contrib/release/verify-tag-hygiene.sh index 4ffacd5b7..3f7577a7a 100755 --- a/contrib/release/verify-tag-hygiene.sh +++ b/contrib/release/verify-tag-hygiene.sh @@ -2,7 +2,9 @@ # # Generic release-tag gate shared by the crate and language-binding release # workflows. Confirms a tag is annotated, signed by a key in -# contrib/release/keys/, and an ancestor of origin/master. +# contrib/release/keys/, and an ancestor of master: origin/master as CI +# checks it out, or the commit in RELEASE_MASTER when a caller has fetched +# master some other way. # # Checks the tag object and repo history only, so it carries no assumption # about what is being released; ecosystem-specific checks (manifest versions, @@ -35,6 +37,7 @@ gpg --homedir "$home" --batch --quiet --import "$REPO_ROOT"/contrib/release/keys GNUPGHOME="$home" git -C "$REPO_ROOT" verify-tag "$tag" >/dev/null 2>&1 || die "$tag is not signed by a trusted key" -echo "Checking the tag is an ancestor of origin/master" -git -C "$REPO_ROOT" merge-base --is-ancestor "$tag" origin/master 2>/dev/null || - die "$tag is not an ancestor of origin/master" +master="${RELEASE_MASTER:-origin/master}" +echo "Checking the tag is an ancestor of $master" +git -C "$REPO_ROOT" merge-base --is-ancestor "$tag" "$master" 2>/dev/null || + die "$tag is not an ancestor of $master" diff --git a/payjoin-cli/CHANGELOG.md b/payjoin-cli/CHANGELOG.md index ddf662a35..f4111e156 100644 --- a/payjoin-cli/CHANGELOG.md +++ b/payjoin-cli/CHANGELOG.md @@ -1,5 +1,7 @@ # payjoin-cli Changelog +## Unreleased + ## 1.0.0-rc.2 Track payjoin 1.0.0-rc.8, whose `check_inputs_not_owned` callback now diff --git a/payjoin-ffi/csharp/RELEASING.md b/payjoin-ffi/csharp/RELEASING.md index a5995ccaa..436f1ffe1 100644 --- a/payjoin-ffi/csharp/RELEASING.md +++ b/payjoin-ffi/csharp/RELEASING.md @@ -116,17 +116,18 @@ is ever stored. The workflow is 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 `payjoin-csharp-`, where `` is the - `Payjoin.csproj` `` exactly, build metadata included. NuGet - strips the metadata from the `Payjoin..nupkg` filename, so - the publish job strips it from the tag as well before comparing. The - tag must be annotated and signed by a maintainer key in - `contrib/release/keys/`, and the tagged commit must be on `master`; - `verify-tag` refuses to publish otherwise. +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 - git tag -s payjoin-csharp-0.1.0+payjoin-1.0.0 -m payjoin-csharp-0.1.0+payjoin-1.0.0 - git push upstream payjoin-csharp-0.1.0+payjoin-1.0.0 + nix develop .#release -c contrib/release/tag.sh csharp ``` The tag reruns the full build/pack/smoke graph at the tagged commit, then diff --git a/payjoin-ffi/csharp/contrib/release-version.sh b/payjoin-ffi/csharp/contrib/release-version.sh new file mode 100755 index 000000000..fc2b1f6e5 --- /dev/null +++ b/payjoin-ffi/csharp/contrib/release-version.sh @@ -0,0 +1,9 @@ +#!/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/CONTRIBUTING.md b/payjoin-ffi/dart/CONTRIBUTING.md index 47fedc818..e70533173 100644 --- a/payjoin-ffi/dart/CONTRIBUTING.md +++ b/payjoin-ffi/dart/CONTRIBUTING.md @@ -58,15 +58,16 @@ validates the archive with a publish dry run 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 `payjoin-dart-`, where `` is the - `pubspec.yaml` version exactly (including the `+` build metadata). The - tag must be annotated and signed by a maintainer key in - `contrib/release/keys/`, and the tagged commit must be on `master`; - `verify-tag` refuses to publish otherwise. +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. ```shell - git tag -s 'payjoin-dart-0.2.1+payjoin-1.0.0-rc.8' -m 'payjoin-dart-0.2.1+payjoin-1.0.0-rc.8' - git push upstream 'payjoin-dart-0.2.1+payjoin-1.0.0-rc.8' + nix develop .#release -c contrib/release/tag.sh dart ``` The tag reruns the tests and the archive verification at the tagged diff --git a/payjoin-ffi/dart/contrib/release-version.sh b/payjoin-ffi/dart/contrib/release-version.sh new file mode 100755 index 000000000..74fa4e79f --- /dev/null +++ b/payjoin-ffi/dart/contrib/release-version.sh @@ -0,0 +1,9 @@ +#!/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/javascript/RELEASING.md b/payjoin-ffi/javascript/RELEASING.md index 84cb87491..67059c4c5 100644 --- a/payjoin-ffi/javascript/RELEASING.md +++ b/payjoin-ffi/javascript/RELEASING.md @@ -32,16 +32,17 @@ TypeScript), which is platform-independent. 1. Confirm every `Build and Test JavaScript` job is green on the release commit in `master`. -2. Tag that commit `payjoin-javascript-`, where `` is the - `package.json` `releaseTag` value exactly; the publish job strips the - build metadata before comparing the tag against the packed tarball. - The tag must be annotated and signed by a maintainer key in - `contrib/release/keys/`, and the tagged commit must be on `master`; - `verify-tag` refuses to publish otherwise. +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 - git tag -s payjoin-javascript-0.2.0+payjoin-1.0.0 -m payjoin-javascript-0.2.0+payjoin-1.0.0 - git push upstream payjoin-javascript-0.2.0+payjoin-1.0.0 + nix develop .#release -c contrib/release/tag.sh javascript ``` The tag reruns the full build/pack/smoke graph at the tagged commit, diff --git a/payjoin-ffi/javascript/contrib/release-version.sh b/payjoin-ffi/javascript/contrib/release-version.sh new file mode 100755 index 000000000..94c86f2e4 --- /dev/null +++ b/payjoin-ffi/javascript/contrib/release-version.sh @@ -0,0 +1,8 @@ +#!/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/python/RELEASING.md b/payjoin-ffi/python/RELEASING.md index 6c56ca1da..c172aaa47 100644 --- a/payjoin-ffi/python/RELEASING.md +++ b/payjoin-ffi/python/RELEASING.md @@ -43,17 +43,18 @@ CPython satisfying `requires-python` can install them. 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 `payjoin-python-`, where `` is the +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; the publish job strips the - metadata before comparing the tag against the built wheels. The tag - must be annotated and signed by a maintainer key in - `contrib/release/keys/`, and the tagged commit must be on `master`; - `verify-tag` refuses to publish otherwise. + 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. ```shell - git tag -s payjoin-python-0.2.0+payjoin-1.0.0 -m payjoin-python-0.2.0+payjoin-1.0.0 - git push upstream payjoin-python-0.2.0+payjoin-1.0.0 + nix develop .#release -c contrib/release/tag.sh python ``` The tag reruns the full build/wheel/smoke graph at the tagged commit, diff --git a/payjoin-ffi/python/contrib/release-version.sh b/payjoin-ffi/python/contrib/release-version.sh new file mode 100755 index 000000000..9af3c7796 --- /dev/null +++ b/payjoin-ffi/python/contrib/release-version.sh @@ -0,0 +1,15 @@ +#!/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/CHANGELOG.md b/payjoin/CHANGELOG.md index d4e484a19..055cbfffe 100644 --- a/payjoin/CHANGELOG.md +++ b/payjoin/CHANGELOG.md @@ -1,5 +1,7 @@ # Payjoin Changelog +## Unreleased + ## 1.2.0 This release adds a non-blocking interface to the receiver state machine.