feat(cli): console arrow keys + history; reject misspelled generator flags - #3611
Merged
Merged
Conversation
This was referenced Sep 13, 2026
bpamiri
added a commit
to wheels-dev/homebrew-wheels
that referenced
this pull request
Sep 13, 2026
#577) Both formulas selected the LuCLI launcher URL with a class-level `if OS.mac? ... elsif OS.linux? ... end`. Current Homebrew evaluates a formula once per bottle platform (golden_gate, arm64_golden_gate, tahoe, ...) and rejects a top-level conditional that leaves `url` undefined on the other branch. The visible symptom, on a fresh Homebrew: ==> Tapping wheels-dev/wheels Error: Invalid formula (golden_gate): .../Formula/wheels-be.rb Error: Cannot tap wheels-dev/wheels: invalid syntax in tap! so `brew tap wheels-dev/wheels` fails outright and `brew install wheels` then reports "No available formula with the name wheels". This breaks every new install, not just CI. Replaced with the on_macos / on_linux block DSL, which is the sanctioned form and what the platform pass expects. The `if OS.mac?` inside `def install` is runtime Ruby and is unaffected. The auto-update workflows bump only the version/sha lines with sed, so this structural change survives future bumps. Surfaced by wheels-dev/wheels#3611 and #3612, whose docs-verify job installs the CLI via this tap. Signed-off-by: Peter Amiri <peter@alurium.com>
bpamiri
added a commit
that referenced
this pull request
Sep 13, 2026
* ci: pin Homebrew/actions/setup-homebrew to a tag Homebrew/actions renamed its default branch from master to main. Both docs workflows referenced @master, so every PR opened after the rename fails at "Set up job" with: Unable to resolve action `homebrew/actions@master`, unable to find version `master` The same job passed 18 hours earlier on PR #3609 with an unchanged workflow — nothing in the repo moved; upstream did. Pinned to the 2026.09.07.1 tag rather than swapping @master for @main: a branch reference is exactly what just broke, and upstream tags weekly. setup-homebrew/action.yml is byte-identical (1421 bytes) at that tag and at today's 2026.09.13.1. Found while landing #3611, whose only red check was this job. Signed-off-by: Peter Amiri <peter@alurium.com> * ci: grant tap trust before tapping Second half of the docs-verify breakage. After pinning the action, the job ran and failed inside `brew tap`: Invalid formula (golden_gate): .../Formula/wheels-be.rb Refusing to load formula wheels-dev/wheels/wheels-be from untrusted tap. Run `brew trust wheels-dev/wheels` to trust it. Homebrew now validates every formula in a tap WHILE cloning it, and on Linuxbrew refuses untrusted ones at that moment. docs-verify already had `brew trust wheels-dev/wheels` — one line AFTER the tap, so it ran too late; docs-validation had no trust at all. Both now trust first. The earlier run proved `brew trust` accepts a not-yet-installed tap (it printed "Trusted tap" after the failed clone). The "Invalid formula" wording is a consequence of the refusal, not a separate syntax problem; the tap's own macOS CI audits and installs the same formula green. Signed-off-by: Peter Amiri <peter@alurium.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com>
Two fixes from the same live-demo rehearsal, plus the test-script change that made verifying them possible alongside a running app. Console arrow keys The REPL read System.in through a BufferedReader, which receives raw bytes: ← arrived as ESC [ D and was echoed as ^[[D. The LuCLI runtime already bundles JLine 3, so the console now uses a LineReader when stdin is a terminal. History persists to ~/.wheels/console_history. Arrow keys are bound explicitly in BOTH cursor modes (ESC [ D and ESC O D). JLine bound only what terminfo advertised — key_left = \EOD — while the terminal actually sends ESC [ D in normal mode, so the escape was consumed and "[D" landed in the buffer as text: `1+2[D[D[D9`. Introspected on the live reader: `emacs ESC[D -> UNBOUND`. When stdin is NOT a terminal (pipes, CI, the tutorial e2e) the plain reader is kept, byte-for-byte as before. Verified: `printf ... | wheels console` exits 0 on a clean session and 1 on a failed eval, unchanged. Unknown generator flags parseGeneratorArgs() dropped any `--` token it did not recognise. A presenter typed `--belogsTo=post` and got a clean-looking scaffold with no association and no parent wiring — nothing in the output hinted why. This is the second flag that loop has swallowed (#2327 was --force). Unknown flags now print a red line with a nearest-match hint and throw Wheels.CLI.UnknownFlag, so the exit code is non-zero and nothing is written. All command-level flags are stripped before this parser runs, so the only legitimate `--` tokens here are the three association flags. Test scripts `PORT=<n>` moved only the HTTP port; lucee.json still pinned shutdown 8081, so both scripts died with "port conflicts detected:" (empty list) whenever any other Wheels app held it — which is exactly the situation when a presenter's demo app is running. Both scripts now pin a free shutdown port next to PORT for the run and restore lucee.json on every exit path. test-local.sh already had the restore half of this (RESTORED_LUCEE_JSON) but nothing ever set it; this completes it. Verified with a live blogdemo on 8080/8081: CLI suite 1363 pass on PORT=8180, framework security area 304 pass on PORT=8190, lucee.json restored byte-for-byte both times, blogdemo untouched. A note on verification, for the record: LUCLI_HOME=/tmp/... does NOT select a module. The brew wrapper exports LUCLI_HOME="$HOME/.wheels" unconditionally, so every earlier "harness" run in this session exercised the installed module. The console-reader closure bug (arguments scope inside a closure, Cross-Engine Invariant 3) was only caught once the worktree Module.cfc was staged into ~/.wheels/modules/wheels directly. CLI suite: 1363 pass, the 4 pre-existing DbCommandSpec failures, 0 errors. Complexity gate: PASS. Signed-off-by: Peter Amiri <peter@alurium.com>
Without allowBlank=true a blank title trips both the presence rule and the exclusion rule, so the empty-form step showed three errors where the card promised two. Found by the presenter on a clean-slate run. The card now explains why the argument is there. Signed-off-by: Peter Amiri <peter@alurium.com>
bpamiri
force-pushed
the
feat/console-line-editing-and-flag-errors
branch
from
September 13, 2026 23:24
b726e43 to
48c9b4a
Compare
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.
Two fixes surfaced by the live-demo rehearsal, plus the test-script change that made verifying them possible alongside a running app.
1.
wheels consolearrow keys and historyThe REPL read
System.inthrough aBufferedReader, which gets raw bytes — ← arrived asESC [ Dand echoed as^[[D. The LuCLI runtime already bundles JLine 3, so the console now uses aLineReaderon a real terminal. ← → move the cursor, ↑ ↓ recall history, Home/End work, and history persists to~/.wheels/console_history.The subtle part. JLine bound arrows only to what terminfo advertised (
key_left = \EOD, application mode), while the terminal actually sendsESC [ Din normal mode. The escape was consumed and[Dlanded in the buffer as text —1+2[D[D[D9. Introspected on the live reader:emacs ESC[D -> UNBOUND. Both forms are now bound explicitly, so it no longer depends on the terminal honouring keypad-transmit mode.Piped sessions are untouched. When stdin isn't a terminal the original reader is kept byte-for-byte. Verified:
printf ... | wheels consoleexits 0 on a clean session and 1 on a failed eval, exactly as before — the contract the tutorial e2e depends on.Driven through a real PTY with real escape sequences:
1+2, ←←←,9, ⏎=> 93(cursor moved to start)=> 93(recalled)=> 93(two back in history)"bc", Home,"a"&, End,&"d", ⏎=> abcd2.
wheels generaterejects misspelled flagsparseGeneratorArgs()dropped any--token it didn't recognise. The presenter typed--belogsTo=postand got a clean-looking scaffold with no association and no parent wiring — nothing in the output hinted why. This is the second flag that loop has swallowed (#2327 was--force).Now:
Red line, exit 1, nothing written. Every command-level flag (
--force,--dry-run) is stripped before this parser runs, so the only legitimate--tokens reaching it are the three association flags — anything else is a typo. 6 new specs, including one pinning that a dash inside a property name is still data.3. Test scripts work beside a running app
PORT=<n>moved only the HTTP port;lucee.jsonstill pinned shutdown 8081, so both scripts died withport conflicts detected:(empty list) whenever any other Wheels app held it — exactly the situation when a demo app is running. Both now pin a free shutdown port for the run and restorelucee.jsonon every exit path.test-local.shalready had the restore half (RESTORED_LUCEE_JSON) but nothing ever set it.Verified with a live
blogdemoon 8080/8081: CLI suite 1363 pass onPORT=8180, frameworksecurity304 pass onPORT=8190,lucee.jsonrestored byte-for-byte both times,blogdemountouched.A verification note worth recording
LUCLI_HOME=/tmp/...does not select a module — the brew wrapper exportsLUCLI_HOME="$HOME/.wheels"unconditionally. Every earlier "harness" run in this session was silently exercising the installed build. A real bug in the console change (argumentsscope inside a closure — Cross-Engine Invariant 3) only surfaced once the worktreeModule.cfcwas staged directly into~/.wheels/modules/wheels.Also folds in the presenter's
allowBlank=trueimprovement to the demo's exclusion rule (three errors → two on a blank form).CLI suite: 1363 pass, 4 pre-existing
DbCommandSpecfailures, 0 errors. Complexity gate: PASS.