Skip to content

Require Rack 3, add CI and Standard (0.2.0) - #1

Merged
rstammer merged 6 commits into
mainfrom
rack3-ci
Aug 2, 2026
Merged

rstammer merged 6 commits into
mainfrom
rack3-ci

Conversation

@rstammer

@rstammer rstammer commented Aug 2, 2026 •

Copy link
Copy Markdown
Owner

Why

0.1.0 declared rack >= 2.2 and did not work on Rack 2.

Rack 3 requires response header keys to be lowercase. Rack 2 left the case alone, and its file server returns Content-Type. The endpoint reads that header to decide whether to inject the <base> tag — so on Rack 2 the injection never fired, and a mounted game served its index.html and then none of its assets. Silent: correct status, correct headers, blank canvas.

Bounding the version rather than adding a case-insensitive lookup. On Rack 3 the lowercase key is a guarantee, so the failure mode stops existing instead of being defended against forever.

The floor is 3.0.3, not 3.0

Found by actually running it. Rack::URLMap up to 3.0.2 calls Regexp.new with the third argument Ruby 3.2 removed, and URLMap backs both Rack::Builder#map and Rails' mount — so on the Ruby this gem now requires, anything below 3.0.3 raises ArgumentError the moment you mount it, which is the only way the gem is ever used.

Changes

  • rack >= 3.0.3, < 4; required_ruby_version >= 3.2 (it said 3.1, but the dev dependencies have needed 3.2 since they were written)
  • CI: suite on Ruby 3.2/3.3/3.4, plus one run pinned to the oldest supported Rack so the lower bound is a tested claim. Separate jobs lint and build-install-require the packaged gem — a gemspec whose files list has a hole still packages fine and only fails for whoever installs it.
  • Standard as the linter, applied across the codebase. rake runs spec + standardrb.
  • 0.2.0, CHANGELOG and README updated.

A note on the linter commits

This branch adopts rubocop-rails-omakase in one commit and replaces it with Standard in a later one. That is the actual sequence of the work rather than a tidied-up version of it, kept because the two configs are worth contrasting: omakase enables 45 cops and is designed to be overridden, Standard enables 353 and is designed not to be. The visible difference is bracket spacing ([ "lib" ] vs ["lib"]), which is why the switch touches every file containing a literal.

Two rewrites in that commit are not whitespace: Dir.children(dir).empty? became Dir.empty?(dir), and a bare rescue StandardError lost the redundant class. Both mean what they meant before.

Verified

CI green on all six jobs. The first run was not, and one commit is the workflow being fixed against itself:

  • an unset matrix axis arrives as RACK_VERSION="", and an empty string is truthy in Ruby, so every unpinned job asked for gem "rack", ""
  • gem install --local forbids the network, so the install-into-an-empty-dir step could not fetch the dependency it exists to check
  • an include entry matching an existing combination is merged into it, so the pinned-rack entry swallowed the plain Ruby 3.2 job rather than joining it

Locally: bundle exec rake green on Ruby 3.4.7, suite green on 3.3.5, across all three rack resolutions (latest 3.2.6, empty env, pinned 3.0.3).

Note

This is a breaking change for anyone on Rack 2, and 0.1.0 is published. In practice nobody could have been running it there, since the feature that breaks is the one the gem exists for.

The reformatting is what rubocop -a did: spaces inside array brackets, no
trailing comma on the last hash entry. No behaviour changed.

rake now runs the suite and the cop, so the default task is the whole check
rather than half of it.
0.1.0 declared rack >= 2.2 and did not work on Rack 2. Rack 3 requires
response header keys to be lowercase; Rack 2 left the case alone and its file
server returns Content-Type. This endpoint reads that header to decide whether
to inject the <base> tag, so on Rack 2 the injection never fired and a mounted
game served its index.html and then none of its assets.

Bounding the version rather than doing a case-insensitive lookup: on Rack 3 the
lowercase key is a guarantee, so the failure mode stops existing instead of
being defended against for good.

The floor is 3.0.3 and not 3.0 because Rack::URLMap up to 3.0.2 calls
Regexp.new with the third argument Ruby 3.2 removed, and URLMap is what backs
Rack::Builder#map and Rails' mount alike.

required_ruby_version said 3.1, but the development dependencies have needed
3.2 since they were written.
The suite on Ruby 3.2, 3.3 and 3.4, plus one run pinned to the oldest Rack the
gemspec claims to support. That pinned run is the point: 0.1.0 shipped a lower
bound nothing had ever executed, and it was wrong twice over — the header case
and Rack::URLMap both moved underneath it.

Separate jobs lint, and build the gem, install it and require it, because a
gemspec whose files list has a hole still packages fine and only fails for
whoever installs it.
Three things the workflow itself got wrong, all of which only showed up by
running it:

An unset matrix axis arrives as RACK_VERSION="", and an empty string is true
in Ruby, so every unpinned job asked for `gem "rack", ""` and failed to
resolve before it ever reached the suite.

gem install --local forbids the network, so installing the built gem into an
empty directory could not fetch the rack it depends on — which is the one thing
that step exists to check.

An include entry whose keys match an existing matrix combination is merged into
it rather than added, so the pinned-rack entry swallowed the plain Ruby 3.2 run
instead of joining it. Spelled out as include entries alone, where each is its
own job.
v4 runs on Node 20, which the runners now force onto Node 24 and warn about on
every job.
@rstammer
rstammer marked this pull request as ready for review August 2, 2026 12:33
@rstammer rstammer self-assigned this Aug 2, 2026
Same engine underneath, opposite stance: omakase enables 45 cops and expects
you to override it, Standard enables 353 and expects you not to. The visible
difference is bracket spacing — [ "lib" ] becomes ["lib"] — which is why this
touches every file that has a literal in it.

Two of the rewrites are not whitespace. Dir.children(dir).empty? became
Dir.empty?(dir), and a bare rescue StandardError lost the redundant class.
Both mean exactly what they meant before.
@rstammer rstammer changed the title Require Rack 3, add CI and RuboCop (0.2.0) Require Rack 3, add CI and Standard (0.2.0) Aug 2, 2026
@rstammer
rstammer marked this pull request as draft August 2, 2026 12:48
@rstammer
rstammer marked this pull request as ready for review August 2, 2026 12:48
@rstammer
rstammer merged commit e22573d into main Aug 2, 2026
6 checks passed
@rstammer
rstammer deleted the rack3-ci branch August 2, 2026 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant