Allow command options to be added externally - #165
Open
afomera wants to merge 1 commit into
Open
Conversation
In hanami-cli, `generate action` declares a `--skip-tests` flag that nothing
in hanami-cli itself uses. It exists only so that hanami-rspec and
hanami-minitest, which hook into the command via `Hanami::CLI.after "generate
action", ...`, have a flag their hooks can read. An unparsed flag is a hard
error, so there was no other way for those gems to accept one.
Add `Registry#command`, which returns the registered command, along with
`Registry#option` and `Registry#argument` as guarded wrappers over it. A
third-party gem can now contribute the param its own hook needs
```ruby
Hanami::CLI.after "generate action", Commands::Generate::Action
Hanami::CLI.option "generate action", :skip_tests,
type: :flag, default: false, desc: "Skip test generation"
```
Because more than one gem may want the same option, adding it twice is a
no-op rather than an error, as long as both declarations agree on :type,
:required, :values and :default. Otherwise IncompatibleOptionError
names the settings that differ. :cast is not compared, because procs aren't
meaningfully comparable.
Note that this extends the command class, so it is not scoped to a single
registration, and a subclass defined before the call won't inherit the param.
Registry already had a private #command returning a LookupResult, which
is renamed to #lookup.
Commands and callbacks are now also passed only the params their #call
declares, so neither side needs a ** catch-all to tolerate params
contributed by another gem. Params are passed through untouched unless #call
declares keywords and no keyword splat, so this only affects signatures that
raise ArgumentError today: a #call declaring no keywords at all (def call(*args), or a callback block) still receives every param, since it reads
them from a single positional Hash.
afomera
force-pushed
the
external-command-params
branch
from
August 15, 2026 06:38
28c7546 to
24d165b
Compare
afomera
marked this pull request as ready for review
August 15, 2026 06:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Per the Hanakai AI Policy: I used AI to help me put together the code changes with my heavy involvement and direction, but I have reviewed every line of code until it reached the quality bar I set for myself for this work.
At no point was AI running without my direction or supervision.
I believe the comments I've left are useful and the specs feel like they've ensured things are tested properly to me, but it is a lot of them. The commit message + PR description was written by me.
In hanami-cli,
generate actiondeclares a--skip-testsflag that nothing in hanami-cli itself uses. It exists only so that hanami-rspec and hanami-minitest, which hook into the command viaHanami::CLI.after "generate action", ..., have a flag their hooks can read. An unparsed flag is a hard error, so there was no other way for those gems to accept one.Add
Registry#command, which returns the registered command, along withRegistry#optionandRegistry#argumentas guarded wrappers over it. A third-party gem can now contribute the param its own hook needsBecause more than one gem may want the same option, adding it twice is a no-op rather than an error, as long as both declarations agree on :type, :required, :values and :default. Otherwise IncompatibleOptionError names the settings that differ. :cast is not compared, because procs aren't meaningfully comparable.
Note that this extends the command class, so it is not scoped to a single registration, and a subclass defined before the call won't inherit the param.
Registry already had a private #command returning a LookupResult, which is renamed to #lookup.
Commands and callbacks are now also passed only the params their #call declares, so neither side needs a ** catch-all to tolerate params contributed by another gem. Params are passed through untouched unless #call declares keywords and no keyword splat, so this only affects signatures that raise ArgumentError today: a #call declaring no keywords at all (def call(*args), or a callback block) still receives every param, since it reads them from a single positional Hash.
Closes #164