From f17ecf5a8cb56271570dda652867152a63dfc99d Mon Sep 17 00:00:00 2001 From: Kevin van Zonneveld Date: Sun, 18 Oct 2026 17:37:09 +0200 Subject: [PATCH] Repair the RubyGems release guide --- CONTRIBUTING.md | 94 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9486b09..015185f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,34 +65,96 @@ The test uploads `chameleon.jpg`, resizes it, and asserts on a real assembly res ## Releasing to RubyGems -1. Update the version and changelog: +Publishing is manual: `scripts/notify-registry.sh` builds and pushes the gem through Docker. Pushing a tag or creating a GitHub release does not publish to RubyGems; `.github/workflows/ci.yml` only runs checks. + +Before changing the version, have Docker running, Ruby 3.1+ (for the release notes and install check), Git, and an authenticated GitHub CLI (`gh`). Confirm you can push signed commits to `main` and push tags. Load `GEM_HOST_API_KEY` as described under [RubyGems Credentials](#rubygems-credentials) before committing or tagging, and configure `TRANSLOADIT_KEY` and `TRANSLOADIT_SECRET` for the live test (the Docker test helper also reads `.env`). + +Run the following steps from the repository root, in the same shell: + +1. Start with a clean checkout (`git status --short` must be empty), then update `main`: + ```bash + git switch main + git pull --ff-only origin main + ``` +2. Update the version and changelog: + - Use semantic versioning: patch for fixes, minor for backward-compatible additions or deprecations, major for breaking changes or removals. - Bump `lib/transloadit/version.rb`. - - Add a corresponding entry to `CHANGELOG.md`. -2. Run the full test suite (including Docker, parity, and e2e checks as needed). -3. Commit the release changes and tag: + - Move the `CHANGELOG.md` Unreleased bullets under `### X.Y.Z / YYYY-MM-DD`, leaving an empty `### Unreleased` heading above them. Use those bullets for the release notes. + - Read the chosen version for the remaining commands, and confirm it has not already been tagged or published: + ```bash + version=$(ruby -Ilib -rtransloadit/version -e 'puts Transloadit::VERSION') + git ls-remote --tags origin "refs/tags/v$version" + gem list transloadit --remote --exact --all --clear-sources --source https://rubygems.org + ``` +3. Run the full Docker suite with Node signature parity and the live upload, then lint: ```bash - git commit -am "Release vX.Y.Z" - git tag -a vX.Y.Z -m "Release vX.Y.Z" + TEST_NODE_PARITY=1 RUBY_SDK_E2E=1 ./scripts/test-in-docker.sh + RUBY_SDK_E2E=0 ./scripts/test-in-docker.sh bundle exec standardrb ``` -4. Push the commit and tag: + Confirm the parity and live tests ran rather than skipped. The live test uploads a fixture to Transloadit and requires working credentials. +4. Commit only the release files with a signed commit, and create the annotated tag: + ```bash + git add lib/transloadit/version.rb CHANGELOG.md + git commit -S -m "Release v$version" + git tag -a "v$version" -m "Release v$version" + ``` +5. Push the commit and tag: ```bash git push origin main - git push origin vX.Y.Z + git push origin "v$version" + ``` + Wait for CI on this exact `main` commit to pass, including all supported Ruby versions and the e2e job. +6. Publish the gem using the helper script with the already-exported key: + ```bash + ./scripts/notify-registry.sh ``` -5. Publish the gem using the helper script: + If authentication or push permissions fail, stop and obtain the required access. If the version is already published, verify it before retrying; RubyGems versions cannot be overwritten. +7. Publish the GitHub release notes from the version's changelog entry. This fails before creating a release if the entry is missing or empty, and requires the pushed tag: ```bash - GEM_HOST_API_KEY=... ./scripts/notify-registry.sh + ( + set -eu + notes_file=$(mktemp) + trap 'rm -f "$notes_file"' EXIT + ruby -e ' + version = ARGV.fetch(0) + entry = File.read("CHANGELOG.md").split(/^### /).find { |section| section.start_with?("#{version} / ") } + abort "No changelog entry for #{version}" unless entry + notes = entry.lines.drop(1).join.strip + abort "Empty changelog entry for #{version}" if notes.empty? + puts notes + ' "$version" > "$notes_file" + gh release create "v$version" --verify-tag --title "v$version" --notes-file "$notes_file" + ) ``` -6. Publish the GitHub release notes: +8. Verify the remote listing, then install the published gem in a scratch directory and check `uploads:` without an API call: ```bash - gh release create vX.Y.Z --title "vX.Y.Z" --notes "$(ruby -e 'puts File.read("CHANGELOG.md")[/^### #{ARGV[0].dump.gsub(/\"/, "\\\"")}/, /\A### /m] || "")'" + gem list transloadit --remote --exact --clear-sources --source https://rubygems.org + ( + set -eu + scratch_dir=$(mktemp -d) + trap 'rm -rf "$scratch_dir"' EXIT + gem install transloadit --remote --clear-sources --source https://rubygems.org --version "$version" --no-document --install-dir "$scratch_dir/gems" + GEM_HOME="$scratch_dir/gems" GEM_PATH="$scratch_dir/gems" ruby -rtransloadit -rstringio -e ' + abort "Unexpected gem version" unless Transloadit::VERSION == ARGV.fetch(0) + fields = {count: 2, enabled: false} + assembly = Transloadit.new(key: "offline-smoke").assembly(uploads: {photo: StringIO.new("photo")}, fields: fields) + instructions = assembly.to_hash + abort "Upload leaked into JSON" if instructions.key?(:uploads) + abort "Custom fields changed" unless instructions[:fields] == fields + puts "transloadit #{Transloadit::VERSION}: uploads smoke passed" + ' "$version" + ) ``` - Adjust the notes if needed before publishing. + `--remote` avoids a leftover local `.gem`, and `--clear-sources --source` selects rubygems.org even when other gem sources are configured. The smoke check builds Instructions without creating an Assembly; step 3 exercises multipart requests. Allow time for RubyGems indexing if the new version is not visible immediately. ### RubyGems Credentials -- You must belong to the `transloadit` organization on RubyGems with permission to push the `transloadit` gem. -- Generate an API key with **Push Rubygems** permissions at . Copy the token and keep it secure. -- Export the token as `GEM_HOST_API_KEY` in your environment before running `./scripts/notify-registry.sh`. The script refuses to run if the variable is missing. +- Use a RubyGems account authorized to push the `transloadit` gem, with an API key granting [**Push rubygems**](https://guides.rubygems.org/api-key-scopes/) permissions (and access to `transloadit` if the key is gem-scoped). +- Export the key as `GEM_HOST_API_KEY` through your secret manager or a separate credential file outside the checkout. Do not put the token in shell commands or logs. The publish helper requires this environment variable; it does not load `.env` or use the host's `~/.gem/credentials`. +- Keep the publishing key out of the test `.env`: `scripts/test-in-docker.sh` passes that entire file to the container running dependency installation, tests, and Node tooling. An exported host `GEM_HOST_API_KEY` is not forwarded by the test helper. +- Confirm the key is loaded without displaying it: + ```bash + test -n "${GEM_HOST_API_KEY:-}" + ``` That’s it! Thank you for contributing.