Skip to content

fix(cli): fresh-VM onboarding fixes (F1, F2, F4, F5, F6, F11) - #2353

Merged
bpamiri merged 6 commits into
developfrom
claude/flamboyant-volhard-27962c
Apr 29, 2026
Merged

bpamiri merged 6 commits into
developfrom
claude/flamboyant-volhard-27962c

Conversation

@bpamiri

@bpamiri bpamiri commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Six framework/CLI fixes from a fresh-VM tutorial run, addressing every blocker that prevented a brand-new user from completing the Getting Started guide. Each finding gets its own commit so the diff reads cleanly per-issue.

# Finding Fix
F1 BuildInfo.cfc had an unescaped # from a PR-suffixed commit subject — bricked every fresh brew install wheels on first request prepare-core.sh CFML-escapes #/" in COMMIT_SUBJECT before sed substitution; inline self-check fails the build on regression
F2 SQLite JDBC driver was staged into lib/ext/ but Lucee 7's datasource resolver only sees OSGi bundles/ — wheels migrate latest failed on every fresh app New BundleStager.stageIntoServerBundles(); wheels start now stages into both targets
F4 wheels generate scaffold Post wrote .resources("post") (singular) to config/routes.cfm, conflicting with the convention Pass pluralName to updateRoutes
F5 wheels reload didn't actually reload — Lucee's inspectTemplate=once keeps the cached .class, so ?reload=true only resets framework state New CfclassesPurger service; wheels reload physically wipes the server's cfclasses cache before triggering reload
F6 wheels stop from a parent / unrelated dir silently registered phantom servers (named after cwd) via LuCLI fallthrough New $isWheelsProjectDir helper; start/stop refuse the fallthrough when no config/settings.cfm is present
F11 wheels browser test ran 0 tests — defaulted to the framework's internal browser-DSL dir and hit the framework test endpoint Default to tests.specs.browser against /wheels/app/tests

What's in scope and what isn't

  • F8 (unencoded @ in nested params) — reproduced and traced: that was a curl + Lucee form-parsing interaction, not a Wheels bug. Browser submits work fine. Will be addressed in the follow-up tutorial PR.
  • F9 (findOne(values=…)) — that named-bind syntax was never implemented; the tutorial just claims it does. Framework behaves correctly. Tutorial PR will rewrite the auth chapter to use a working approach (dynamic finder).
  • F11 sub-issues out of scope:
    • Running browser specs corrupts the dev server's cfclasses cache for other specs (the F5 fix should help but doesn't fully resolve it)
    • WHEELS_ENV=test wheels migrate latest looks for a wheelstestdb_sqlite datasource that wheels new doesn't configure

Test plan

  • Local CLI suite green: 462 → 473 pass (+11 new specs across BundleStagerSpec, CfclassesPurgerSpec, ScaffoldSpec; the same 3 pre-existing DoctorSpec failures from packages: doctor mixin-collision scan is regex-based and may miss metadata-driven mixins #2260)
  • Local framework suite green: 3347 passed (no regressions)
  • F1: end-to-end simulated prepare-core.sh against a temp git repo with the bug-triggering commit subject feat(config): introduce BuildInfo.cfc as the runtime version source (#2352) → produced BuildInfo.cfc reads (##2352) (valid CFML); reverting the escape pipeline triggers the self-check with exit 1
  • F2: verified the SQLite bundle is staged into both ~/.wheels/express/<v>/lib/ext/ AND ~/.wheels/servers/<name>/lucee-server/bundles/ after wheels start
  • Pending CI: full compat matrix on PR (Lucee 5/6/7 + Adobe 2018-2025 × MySQL/Postgres/SQL Server/H2/SQLite/CockroachDB)

bpamiri added 6 commits April 28, 2026 21:54
prepare-core.sh sed-substitutes the latest commit subject into
BuildInfo.cfc inside a double-quoted CFML string. # is CFML's
variable-interpolation delimiter, so a commit subject ending in a
parenthesized PR ref like "(#2352)" produces a syntactically broken
.cfc that crashes Lucee on the first request — bricking every fresh
brew/chocolatey install. Onboarding finding F1.

Escape both # → ## and " → "" before the substitution, then escape sed
replacement specials (\, &) so the value transports through the
existing sed pipeline cleanly. Add an inline post-substitution check
that fails the build if any unescaped # or " remains in the produced
commitSubject literal — a regression-stop without standing up a new
test harness.

Verified end-to-end: with COMMIT_SUBJECT="feat(config): introduce
BuildInfo.cfc as the runtime version source (#2352)", the produced
BuildInfo.cfc reads `commitSubject: "...source (##2352)",` (valid
CFML); reverting the escape pipeline triggers the self-check with
exit 1.
generateScaffold() takes a singular model name (e.g. "Post") and
correctly derives a pluralized form for the controller, views, and
migration — but updateRoutes() was being passed the raw singular,
producing .resources("post") in routes.cfm. Onboarding finding F4.

Wheels' resource convention is plural (PostsController maps to
.resources("posts")), so the singular variant either creates a phantom
second route that conflicts with any hand-added plural route, or
silently fails to bind to the controller scaffolded a few lines
earlier. The scaffold runs through cleanly otherwise — this is the one
piece that drifted from the convention.

Pass pluralName, which is already computed for the model/controller/
view generation right above. Add a regression spec that asserts
routes.cfm contains .resources("articles") (plural) and not
.resources("article") (singular) after scaffolding "Article".
The previous fix (#2349) staged the SQLite JDBC driver into every
Lucee Express install's lib/ext/ — Tomcat's parent classpath. That
satisfies Class.forName("org.sqlite.JDBC") but Lucee 7's datasource
resolver consults its OSGi bundle loader for driver instantiation,
not the Tomcat classloader. Without the bundle in
<server>/lucee-server/bundles/, the very first cfquery against a
SQLite datasource fails — and the Wheels migrator goes through
cfquery, so `wheels migrate latest` blows up on every fresh app.
Onboarding finding F2.

Other engines (MySQL, Postgres) avoid the issue because their bundles
ship inside lucee-7.x.x.jar and Lucee auto-deploys them; the SQLite
extension is third-party and doesn't get the same treatment, so
seeding bundles/ is on us.

Add BundleStager.stageIntoServerBundles() alongside the existing
stageIntoLibExt() — same idempotent / best-effort contract, walks
LUCLI_HOME/servers/<name>/lucee-server/bundles/. Wire it into
$ensureWheelsBundles() so every wheels start populates both targets.
The OSGi-symbolic-name filename (org.xerial.sqlite-jdbc-3.49.1.0.jar)
is required for Lucee's bundle loader to recognize it.

5 new specs cover detection, idempotency, missing serversRoot,
missing bundleSrc, and partial-server-context tolerance. CLI suite
462 → 467 pass on this worktree.
LuCLI's dev-server Lucee config defaults to `inspectTemplate=once`,
which means Lucee compiles each CFC once, caches the .class on disk
under `<server>/lucee-server/context/cfclasses/`, and never rechecks
the source timestamp. Wheels' `?reload=true` resets framework state
via applicationStop() but does not invalidate Lucee's template cache,
so source edits to models, controllers, and config silently keep
returning the old compiled class. Even `wheels stop && wheels start`
doesn't help — the .class on disk persists. The only path that picks
up changes is physically wiping cfclasses. Onboarding finding F5; the
single-biggest time sink across the tutorial run.

`wheels reload` now physically wipes the cfclasses dir for the
project's registered server before triggering the HTTP reload. Order
matters: purge first, then reload — the application reset on the
next request gets the fresh compile.

Extract the deletion into CfclassesPurger.cfc (matching the
BundleStager extraction pattern) so the logic is unit-testable
without touching a live server. 5 new specs cover happy path, dir
preservation, missing path, empty path, and idempotency.

Best-effort: per-file failures (Lucee-locked .class mid-compile,
Windows file lock) are swallowed rather than aborting the reload —
the user is better served by a partial-purge than a full crash.
LuCLI's `server start` and `server stop` derive the server name from
the cwd basename when no registered server matches. Running `wheels
start` or `wheels stop` from a parent directory or unrelated dir
(easy to do by accident, especially in shell sessions where cwd
resets between commands) silently registers a phantom server context
named after that dir. The user ends up with ghost entries like `ws`
or `Downloads` in `wheels server list`, with `<unknown>` webroots
and no path to clean up. Onboarding finding F6.

Add a `$isWheelsProjectDir(path)` helper that checks for
`config/settings.cfm` — the file `wheels new` always writes and that
no other tool creates (box.json and Application.cfc are not specific
enough).

  - `wheels start` refuses up-front when not in a Wheels project,
    pointing the user at `wheels new`.
  - `wheels stop` already had a guard for the case where the cwd has
    no registered server but other servers are running (reports them);
    extend it to also refuse fallthrough when no servers are running
    at all, so we never reach LuCLI's name-from-cwd code path.
Two bugs in `wheels browser test` made it useless from the app for
running browser specs. Onboarding finding F11.

  - Default directory was `wheels.tests.specs.wheelstest`, the
    framework's internal browser-DSL test directory. Apps live at
    `tests/specs/browser/`, so the runner reported 0 tests against
    a fresh app spec.

  - Test URL hit `/wheels/core/tests`, the framework's core test
    runner. That endpoint only knows about specs under
    `vendor/wheels/tests/specs/`. App-side specs are mounted by
    `/wheels/app/tests`, the app test runner.

Default directory is now `tests.specs.browser` and the runner hits
`/wheels/app/tests`. `--directory=...` still overrides for advanced
use.

Two related sub-issues from the same finding (running browser specs
corrupts the dev server's cfclasses cache for other specs;
WHEELS_ENV=test wheels migrate latest looks for an unconfigured
`wheelstestdb_sqlite` datasource) are out of scope for this fix and
tracked separately.
@bpamiri
bpamiri merged commit 9b642ea into develop Apr 29, 2026
4 checks passed
@bpamiri
bpamiri deleted the claude/flamboyant-volhard-27962c branch April 29, 2026 05:28
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