Skip to content

fix(multiple): three journal-triage fixes (#2316, F17, #2319)#2350

Merged
bpamiri merged 3 commits intodevelopfrom
claude/pedantic-cori-bae999
Apr 28, 2026
Merged

fix(multiple): three journal-triage fixes (#2316, F17, #2319)#2350
bpamiri merged 3 commits intodevelopfrom
claude/pedantic-cori-bae999

Conversation

@bpamiri
Copy link
Copy Markdown
Collaborator

@bpamiri bpamiri commented Apr 28, 2026

Three independent fixes from the fresh-VM journal triage. Each is its own commit and each maps to its own issue/finding. Reviewing as one PR because they're all small and conceptually related (post-mortem of the same VM cycle), but they could land separately if reviewers prefer.

Commit 1 — fix(cli): wheels stop lists running servers when cwd doesn't match — closes #2316

Running wheels stop from any directory that wasn't a registered project root (parent dir, sibling dir, deleted project dir) silently printed No running server found for this directory. while the original Java/Catalina process kept listening.

After: when no registered server matches the cwd but other Wheels servers are running, list them with name + port + project path and point users at wheels server stop --name <name> and wheels server list. Normal in-project wheels stop is unaffected.

Commit 2 — fix(model): migrator emits symmetric DDL for empty default across string-like types — closes journal F17

addColumnOptions had a type=='string' && default=="" special case that omitted the DEFAULT clause, but text and char columns fell through and emitted DEFAULT ''. That asymmetry made validatesPresenceOf fire inconsistently between equivalent columns — tutorial chapter 7's requires a body spec failed because of it.

After: extend the special case to cover `string,text,char` uniformly. 7 new specs in `addColumnOptionsSpec.cfc`.

Commit 3 — fix(view): wheels-typed error pages set HTTP status (404 / 500), not 200 — closes #2319

`$runOnError` rendered Wheels-typed exceptions in HTML format without setting a status code. Lucee defaulted to 200 — misleading anything monitoring/alerting/retrying on status codes.

After: any `Wheels.*NotFound` → 404, everything else → 500. Status set before the body so the response header commits at the right code. 5 new specs in `onerrorSpec.cfc` lock the mapping table.

Verification

  • Framework suite: 3347 pass, 0 fail (was 3333 before this chain; +14 = 7 from F17, 7 from fix(view): dev error page returns HTTP 200 instead of 5xx #2319). The pre-existing `assertNotFound()` test still passes — `Wheels.ViewNotFound` (raised by an unknown route) now correctly maps to 404.
  • CLI suite: 457 pass, 3 fail — the same pre-existing DoctorSpec failures (#2260), unrelated.
  • End-to-end: `wheels stop` from a parent dir prints the orphan-recovery list; tutorial-style `t.text(default="")` migration produces a column without `DEFAULT ''`; `curl /nonexistent-route` now returns HTTP 404.

Also closed during triage (no PR needed)

  • #2322 and #2310 — already resolved by #2341; closed as completed.
  • #2325 (`this.env` access error on Windows) — couldn't reproduce on macOS, requested Lucee + framework version + Application.cfc setup details to narrow.

Deferred

  • Journal F13 (`wheels reload` doesn't expire model class cache) — needs a deeper debug session to nail down whether it's Lucee CFC bytecode caching or a CLI redirect-follow issue. Punt.

🤖 Generated with Claude Code

bpamiri added 3 commits April 28, 2026 09:39
…registered project

Closes #2316. Before this PR, running `wheels stop` from any directory
that isn't a registered project root (parent dir, sibling dir, deleted
project dir, anywhere else) silently printed "No running server found
for this directory." while the original Java/Catalina process kept
listening. The user's only escape hatch was `lsof -i :<port>` + `kill`.

This change adds a pre-delegation check in stop():

1. `$findServerForProject(projectRoot)` scans `~/.wheels/servers/*/.project-path`
   for an entry whose stored canonical path matches the current cwd.
2. If no match is found, `$listRunningWheelsServers()` enumerates all
   registered LuCLI server entries with a live PID (parsing the
   `<pid>:<port>` server.pid format and using `java.lang.ProcessHandle`
   for liveness without shelling out).
3. When there are running servers but none match the cwd, print a
   helpful list with each server's name + port + project path, plus
   the explicit syntax — `wheels server stop --name <name>` and
   `wheels server list` — that recovers from the orphan state without
   leaving the wheels CLI.

Verified end-to-end on macOS arm64:

  $ cd /tmp/parent       # not a Wheels project
  $ wheels stop
  Stopping Wheels server...

  No registered server matches this directory.
  Running Wheels servers:
    - repapp (port 9991, project /private/tmp/parent/repapp)

  To stop a specific server: wheels server stop --name <name>
  To list all servers:      wheels server list

Normal in-project `wheels stop` is unaffected — the registered-server
match short-circuits the orphan check and falls through to the regular
LuCLI delegation. CLI suite: 457 pass, 3 fail (pre-existing DoctorSpec
#2260, unrelated).
…ing-like types

Closes fresh-VM journal F17. Before this PR, `addColumnOptions` in
`vendor/wheels/databaseAdapters/Abstract.cfc` had a special case for
`type='string'` with `default=""` that omitted the DEFAULT clause:

  } else if (arguments.options.type == 'string' && arguments.options.default eq "") {
      arguments.sql = arguments.sql;          // no DEFAULT clause
  } else {
      arguments.sql = arguments.sql & " DEFAULT ...";
  }

…but `text` and `char` columns fell through to the else branch and
emitted `DEFAULT ''`. So a migration that declared title and body
identically:

  t.string(columnNames="title", default="", allowNull=true, limit=255);
  t.text(columnNames="body",  default="", allowNull=true);

…produced asymmetric DDL. That asymmetry then interacted with
`validatesPresenceOf` — which checks the column's introspected
`hasDatabaseColumnDefault` and skips presence-check when a default
exists — making the user's `validatesPresenceOf` rule fire for `title`
(no DEFAULT clause emitted) but silently skip for `body` (DEFAULT ''
emitted). Tutorial chapter 7's model spec `requires a body` failed
because of this; an HTTP request that omits `post[body]` (vs sending
empty string) also slipped past validation.

Fix: extend the special case to cover all string-like types
(`string,text,char`). Empty default → no DEFAULT clause for all three;
explicit non-empty defaults still emit DEFAULT correctly.

Seven new specs in `addColumnOptionsSpec.cfc` cover:
- string/text/char with default="" all omit DEFAULT
- string/text with non-empty default still emit DEFAULT '<value>'
- integer with default="" still becomes DEFAULT NULL (regression for
  the existing typed-numeric branch)
- boolean with default=true still emits DEFAULT 1

All 7 pass; framework suite: 3340 pass, 0 fail (was 3333 before).
Closes #2319. Before this PR, when `$runOnError` rendered a
Wheels-typed exception (`Wheels.RouteNotFound`,
`Wheels.DataSourceNotFound`, `Wheels.ViewNotFound`, etc) in HTML
format, no `$header(statusCode = ...)` fired before the body was
written. Lucee defaulted to HTTP 200 — misleading anything monitoring,
caching, retrying, or alerting on status codes.

JSON and XML branches for non-Wheels exceptions had explicit
`$header(statusCode = 500)`; the Wheels-error branch and the JSON/XML
Wheels-error sub-branches all skipped status assignment.

This change adds a single mapping at the top of the wheelsError branch:

- Any `Wheels.*NotFound` (RouteNotFound, RecordNotFound, ViewNotFound,
  PackageNotFound, DataSourceNotFound, …) → 404
- Everything else → 500

The status is set before the body is written so the response header
commits at the right code regardless of when the servlet engine
flushes. `$throwErrorOrShow404Page` already calls $header(statusCode=404)
before throwing, but the onError flow can reset the response, so
re-asserting in $runOnError is the durable place.

Five new specs in onerrorSpec.cfc lock the mapping table — they mirror
the same regex used in EventMethods so a rename there breaks the
build immediately. Includes an explicit case for ActionParameterMissing
(Missing != NotFound, stays 500) to guard against a too-greedy regex
later.

Framework suite: 3347 pass, 0 fail (was 3333 before this PR's chain;
+7 from F17 specs, +7 from these new mappings). One pre-existing test
that hits an unknown route and expects 404 (testClientSpec
`assertNotFound() passes on 404 response`) still passes — Wheels.ViewNotFound
maps to 404 under the new rule.
@bpamiri bpamiri merged commit bdf3f51 into develop Apr 28, 2026
4 checks passed
@bpamiri bpamiri deleted the claude/pedantic-cori-bae999 branch April 28, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(view): dev error page returns HTTP 200 instead of 5xx fix(cli): wheels stop reports 'no running server' while server is alive

1 participant