Skip to content

Commit 8dede6b

Browse files
bpamiriclaude
andcommitted
Merge origin/develop into fix/bot-3115-upgrade-check-upgrade-guide-recommend-set-csrfencr
Reconciles this bot PR with #3123, which rewrote the 3x-to-4x upgrade guide's CSRF-key section on develop after this branch was cut. Resolution: - 3x-to-4x.mdx: develop's version wins wholesale — #3123 already covers everything this branch changed (csrfCookieEncryptionSecretKey naming, csrfStore="cookie" scoping, production-throws note, the #3115 caution Aside, and the corrected Common-issues bullet), strictly superseding this branch's three hunks. - Module.cfc, UpgradeCheckCoverageSpec.cfc, upgrade.mdx, and the changelog.d fragment auto-merged cleanly and remain this branch's contribution (the upgrade-check rule fix itself is still absent from develop). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com>
2 parents 87f9339 + bb98ffe commit 8dede6b

96 files changed

Lines changed: 2000 additions & 541 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ai/wheels/troubleshooting/common-errors.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ Wheels failed to initialize. Check the server log for details.
2929

3030
**Note:** Before the #2774 fix, this failure cascaded into a second `[WO] does not exist` exception that hid the real cause. If you see the old cascade on a version that predates this fix (i.e. 4.0.1 or earlier), the underlying cause is always a failed `onApplicationStart` — see above.
3131

32+
### "key [ENGINEADAPTER] doesn't exist" / "Element WHEELS.ENGINEADAPTER is undefined" in dev error page
33+
**Error (on-page or in server log):**
34+
```
35+
key [ENGINEADAPTER] doesn't exist (Lucee)
36+
Element WHEELS.ENGINEADAPTER is undefined (Adobe CF)
37+
```
38+
39+
**Cause:** An exception during `onApplicationStart` (e.g. `Wheels.Cors.InvalidConfiguration` from an invalid `config/settings.cfm` value) triggered `onError`, which itself crashed because three request-lifecycle helpers — `$getRequestTimeout()`, `$statusCode()`, and `$contentType()` — read `application.wheels.engineAdapter` directly after gating on `$hasEngineAdapter()`. That gate checks both `application.wheels` and the startup-staging struct `application.$wheels`, but the subsequent read assumed the adapter had been promoted to `application.wheels`. When only the `$wheels` branch matched (the failed-startup state), the read threw and replaced the original exception (fixed in [#3108](https://github.com/wheels-dev/wheels/pull/3108)).
40+
41+
**Resolution:**
42+
The `[ENGINEADAPTER]` crash is a symptom — the real error happened during startup. Check the server log for the original `onApplicationStart` exception; common causes include invalid middleware configuration, a missing CFML mapping, or a syntax error in `config/settings.cfm` or `config/routes.cfm`.
43+
44+
After upgrading past the #3108 fix, `onError` surfaces the original startup exception directly.
45+
3246
## Common Association Errors
3347

3448
### "Missing argument name" in hasMany()

CLAUDE.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Model finders return query objects, not arrays. Loop accordingly.
107107
.end()
108108
```
109109

110+
`scope()`, `namespace()`, `package()`, and `controller()` also accept `callback=` and auto-close the scope when the callback returns — use the same callback form for these too (#3072).
111+
110112
### 4. HTML5 Form Helpers Exist — Use Them
111113
```cfm
112114
#emailField(objectName="user", property="email")#
@@ -259,7 +261,7 @@ For new migrator helpers or anywhere you accept a column-name argument: declare
259261
component extends="Model" {
260262
function config() {
261263
// Table/key (only if non-conventional)
262-
table("tbl_users"); // setter is table(); tableName() is a getter — tableName("x") is a silent no-op (#3079)
264+
table("tbl_users"); // setter is table(); tableName() is a getter — tableName("x") throws Wheels.InvalidArgument in dev/testing, no-op in production (#3079)
263265
setPrimaryKey("userId");
264266
265267
// Associations — all named params when using options
@@ -349,8 +351,9 @@ Resolves `params.key` into a model instance before the action runs. Lands in `pa
349351
```cfm
350352
.resources(name="users", binding=true) // params.user
351353
.resources(name="posts", binding="BlogPost") // params.blogPost
352-
.scope(path="/api", binding=true) // all nested resources bound
353-
.end()
354+
.scope(path="/api", binding=true, callback=function(map) { // all nested resources bound
355+
map.resources("users");
356+
})
354357
set(routeModelBinding=true); // global, in config/settings.cfm
355358
```
356359

@@ -406,9 +409,9 @@ set(middleware = [
406409
407410
// config/routes.cfm — route-scoped
408411
mapper()
409-
.scope(path="/api", middleware=["app.middleware.ApiAuth"])
410-
.resources("users")
411-
.end()
412+
.scope(path="/api", middleware=["app.middleware.ApiAuth"], callback=function(map) {
413+
map.resources("users");
414+
})
412415
.end();
413416
```
414417

@@ -713,7 +716,7 @@ User-facing `fix`/`feat` PRs add a **fragment file**, never a direct `CHANGELOG.
713716

714717
There is no `wheels mcp setup` command — copy the JSON above into `.mcp.json` manually (see the MCP integration guide for OpenCode/Cursor variants).
715718

716-
Tools are auto-discovered from `cli/lucli/Module.cfc` public functions, prefixed with the module name (`wheels_generate`, `wheels_migrate`, `wheels_test`, `wheels_reload`, `wheels_seed`, `wheels_analyze`, `wheels_validate`, `wheels_routes`, `wheels_info`, `wheels_destroy`, `wheels_doctor`, `wheels_stats`, `wheels_notes`, `wheels_db`, `wheels_upgrade`, `wheels_create`, `wheels_deploy`, `wheels_packages`). CLI-only tools (`main`, `mcp`, `d`, `g`, `new`, `console`, `start`, `stop`, `browser`) are hidden via `mcpHiddenTools()`.
719+
Tools are auto-discovered from `cli/lucli/Module.cfc` public functions. Names in `tools/list` are the bare function names — NOT `wheels_*`-prefixed (live-verified on the released 4.0.3 CLI): `analyze`, `create`, `db`, `deploy`, `destroy`, `doctor`, `generate`, `info`, `migrate`, `notes`, `packages`, `reload`, `routes`, `seed`, `stats`, `test`, `upgrade`, `validate` (18 tools; the `wheels` server entry in `.mcp.json` namespaces them per client). CLI-only tools (`main`, `mcp`, `d`, `g`, `new`, `console`, `start`, `stop`, `browser`) are hidden via `mcpHiddenTools()`.
717720

718721
**Deprecated:** the in-dev-server HTTP endpoint at `/wheels/mcp`. Emits a deprecation notice on first request. Migrate to the stdio surface.
719722

@@ -725,13 +728,13 @@ Prefer MCP tools when the Wheels MCP server is available. Fall back to CLI other
725728

726729
| Task | MCP | CLI |
727730
|------|-----|-----|
728-
| Generate | `wheels_generate(type, name, attributes)` | `wheels g model/controller/scaffold Name attrs` |
729-
| Migrate | `wheels_migrate(action="latest\|up\|down\|info\|doctor")` | `wheels migrate latest\|up\|down\|info\|doctor` |
731+
| Generate | `generate(type, name, attributes)` | `wheels g model/controller/scaffold Name attrs` |
732+
| Migrate | `migrate(action="latest\|up\|down\|info\|doctor")` | `wheels migrate latest\|up\|down\|info\|doctor` |
730733
| Migrator reconciliation || `wheels migrate forget\|pretend <version> --yes` (shared dev DB orphan cleanup; see #2780) |
731-
| Test | `wheels_test()` | `wheels test` |
732-
| Reload | `wheels_reload()` | `?reload=true&password=...` |
734+
| Test | `test()` | `wheels test` |
735+
| Reload | `reload()` | `?reload=true&password=...` |
733736
| Server || `wheels start\|stop` |
734-
| Analyze | `wheels_analyze(target="all")` ||
737+
| Analyze | `analyze(target="all")` ||
735738
| Admin || `wheels g admin ModelName` |
736739
| Seed || `wheels seed` |
737740

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
- A compile-time or runtime error in a `config/*.cfm` file loaded during application start (e.g. an Adobe CF-illegal top-level `var di = injector();` in `config/services.cfm`) is now caught by `$includeConfig()`, logged with the offending file name, and skipped — instead of cascading out of `onApplicationStart` into a masked, application-wide HTTP 500 whose secondary `onError` failure hid the real cause (#3063)
1+
- A compile-time or runtime error in a `config/*.cfm` file loaded during application start (e.g. an Adobe CF-illegal top-level `var di = injector();` in `config/services.cfm`) is now logged and rethrown by `$includeConfig()` as a named `Wheels.ConfigIncludeFailed` error carrying the failing template path and the original engine message (original type/detail preserved in `detail`) — a clear, located, fail-closed boot error in every environment, instead of the old masked, application-wide HTTP 500 whose secondary `onError` failure hid the real cause. An app whose config did not load no longer boots fail-open on framework defaults (#3063)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Router: `scope()` (and the `namespace()` / `package()` / `controller()` wrappers that forward to it) silently dropped a `callback=` argument — the callback never ran (its routes 404'd as `Wheels.RouteNotFound`) and nothing closed the scope, so every route declared afterward (including `resources`, `root`, and `wildcard`) inherited the scope's path prefix and middleware. `scope()` now consumes `callback` the same way `group()` does: it runs `callback(this)` to declare the nested routes and auto-closes the scope, so the documented `.scope(path="/api", callback=function(map){ ... })` and `.namespace(name="admin", callback=...)` forms work as written and routes after the block are no longer swallowed (#3072)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- The action-dispatch gate now returns HTTP 404 (not 500) when a request targets a framework helper or `$`-prefixed internal method, matching the missing-action behavior promised by #2845 and CLAUDE.md Anti-Pattern 8. `$callAction` routes the block through `$throwErrorOrShow404Page` (committing the 404 header and rendering the production 404 page), and the `EventMethods` status map now treats `Wheels.ActionNotAllowed` as a 404 alongside the `Wheels.*NotFound` family (#3075)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- The development error page no longer masks app-start exceptions: `$getRequestTimeout()`, `$statusCode()`, and `$contentType()` now resolve the engine adapter via `$engineAdapter()` (which checks both `application.wheels` and the `application.$wheels` startup-staging struct) instead of reading `application.wheels.engineAdapter` unconditionally. Previously, when a throw during settings/routes load left the adapter only in `application.$wheels`, `onError` itself crashed with `key [ENGINEADAPTER] doesn't exist` (Lucee) / `Element WHEELS.ENGINEADAPTER is undefined` (Adobe), hiding the original failure such as the `Wheels.Cors.InvalidConfiguration` guard (#3076)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `sendFile()`: an absolute `directory` argument is now used verbatim instead of being post-processed, honouring the documented "must be a full path … outside of the web root" contract. Previously the resolver ran the caller-supplied absolute path through the `/wheels` mapping rewrite — substring-hijacking any directory containing `/wheels` (e.g. `/var/www/wheels/uploads`) on every engine — and through an `ExpandPath()` fallback that web-root-prefixed the path on Adobe CF, so serving files from outside the web root threw `Wheels.FileNotFound` there. The verbatim branch only engages when the directory actually exists on disk, so the long-standing webroot-relative idiom (`directory="/reports/"`) keeps resolving against the web root via `ExpandPath()`. Relative `filePath`-based resolution and the `..`-traversal guard are unchanged (#3077)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Calling `tableName()` with an argument now throws `Wheels.InvalidArgument` when error information is shown (development/testing) instead of silently no-opping — some 4.0-era docs mistakenly taught `tableName("x")` as the table setter, leaving models on their convention table. The zero-argument getter is unchanged, production stays a no-op, and the canonical setter remains `table()` (#3079)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- CLI: migration failures now reach the exit code. A failed `up()`/`down()` step that `migrateTo()` folds into its output (e.g. `Error migrating to <version>.`) made `wheels migrate latest|up|down` print the error inside the green success block and still exit 0; `wheels db reset --force` exited 0 when it refused on a `ServerNotRunning` check; and `wheels migrate forget|pretend` refusals (not in tracking table, matching local file exists, already applied, no matching file) printed red but exited 0. All three now exit non-zero, so a `wheels migrate latest && …` CI gate no longer proceeds as if the schema moved. Informational dry-run output (missing `<version>` / missing `--yes`) still exits 0. The migrate-side sibling of the #2973/#2987 seeder honesty fix (#3081)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `wheels seed --generate` now actually creates rows and reports honest success/failure. The CLI seed bridge's generate loop iterated `$classData().properties` (a struct keyed by property name) as if it were an array of property structs — so `prop.name` threw `there is no property with name [NAME] found in [string]`, every model errored, zero rows were created, yet the run still returned `success=true` and the CLI printed "Seeding completed." with exit 0. The generate path is now a dedicated, unit-tested `wheels.Seeder.generateSeeds(models, count)` method that iterates the property struct correctly and forces overall `success=false` when any model fails or no rows are created, so the CLI surfaces a non-zero exit instead of silently lying (the #2987 honesty fix had only covered convention mode) (#3082)

0 commit comments

Comments
 (0)