Skip to content

feat(cli): console arrow keys + history; reject misspelled generator flags - #3611

Merged
bpamiri merged 2 commits into
developfrom
feat/console-line-editing-and-flag-errors
Sep 13, 2026
Merged

bpamiri merged 2 commits into
developfrom
feat/console-line-editing-and-flag-errors

Conversation

@bpamiri

@bpamiri bpamiri commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Two fixes surfaced by the live-demo rehearsal, plus the test-script change that made verifying them possible alongside a running app.

1. wheels console arrow keys and history

The REPL read System.in through a BufferedReader, which gets raw bytes — ← arrived as ESC [ D and echoed as ^[[D. The LuCLI runtime already bundles JLine 3, so the console now uses a LineReader on 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 sends ESC [ D in normal mode. 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. 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 console exits 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:

keys sent result
1+2, ←←←, 9, ⏎ => 93 (cursor moved to start)
↑, ⏎ => 93 (recalled)
↑↑, ⏎ => 93 (two back in history)
"bc", Home, "a"&, End, &"d", ⏎ => abcd

2. wheels generate rejects misspelled flags

parseGeneratorArgs() dropped any -- token it didn't recognise. The 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).

Now:

Unknown flag --belogsTo. Did you mean --belongsTo? Association flags are --belongsTo=, --hasMany=, --hasOne=.

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.json still pinned shutdown 8081, so both scripts died with port 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 restore lucee.json on every exit path. test-local.sh already had the restore half (RESTORED_LUCEE_JSON) but nothing ever set it.

Verified with a live blogdemo on 8080/8081: CLI suite 1363 pass on PORT=8180, framework security 304 pass on PORT=8190, lucee.json restored byte-for-byte both times, blogdemo untouched.

A verification note worth recording

LUCLI_HOME=/tmp/... does not select a module — the brew wrapper exports LUCLI_HOME="$HOME/.wheels" unconditionally. Every earlier "harness" run in this session was silently exercising the installed build. A real bug in the console change (arguments scope inside a closure — Cross-Engine Invariant 3) only surfaced once the worktree Module.cfc was staged directly into ~/.wheels/modules/wheels.

Also folds in the presenter's allowBlank=true improvement to the demo's exclusion rule (three errors → two on a blank form).

CLI suite: 1363 pass, 4 pre-existing DbCommandSpec failures, 0 errors. Complexity gate: PASS.

@github-actions github-actions Bot added the docs label 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
bpamiri force-pushed the feat/console-line-editing-and-flag-errors branch from b726e43 to 48c9b4a Compare September 13, 2026 23:24
@bpamiri
bpamiri merged commit f0abd6a into develop Sep 13, 2026
13 checks passed
@bpamiri
bpamiri deleted the feat/console-line-editing-and-flag-errors branch September 13, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant