Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All historical references to "CFWheels" in this changelog have been preserved fo

### Fixed

- CLI-audit tail polish (follow-up to the #2882–#2886 audit sweep): `wheels info` now renders the framework-version line again — it read the long-gone `vendor/wheels/events/onapplicationstart/settings.cfm` path and silently printed nothing, so it now reads the authoritative `vendor/wheels/wheels.json` manifest by absolute path (no `wheels` mapping needed) and applies the same structural placeholder guard as `wheels.BuildInfo` (an unstamped dev checkout reports `0.0.0-dev` rather than leaking the raw `@build.version@` token). Two internal `$`-prefixed test helpers (`$normalizeTestFilter`, `$resolveAppTestDataSource`) were leaking into the MCP `tools/list` as callable tools — they are now listed in `mcpHiddenTools()` (kept `public` so `TestCommandSpec` can still unit-test them directly; LuCLI matches hidden names case-insensitively). `wheels --help` now lists the `create app` command (it was a working command + MCP tool but absent from the banner) and its `notes` line no longer advertises a `HACK` default the parser doesn't use (the default stays `TODO,FIXME,OPTIMIZE`; `--annotations` customizes it). `wheels reload` now honors an explicit `--password=<value>` override (parity with `wheels console`; auto-detect from `.env`/config remains the default). The interactive console `/help` now lists the `/datasource` and `/q` aliases it already accepts. The `wheels mcp` instructions and the deprecated `/wheels/mcp` endpoint's deprecation notice now point to the live MCP integration guide instead of a doc path (`mcp-configuration-guide.md`) that never existed. Docs: the `wheels test` flag table documents the real `--directory` alias for `--filter`, and the agent `CLAUDE.md` packages block lists the real `wheels packages registry info` verb.
- `wheels reload` and `wheels generate admin` now refuse to attach to a server that isn't bound to the current project, closing the same #2878 gap for two more server-dependent commands that #2879 fixed for the write-side migrators. Both reached `cli.lucli.Module::$requireRunningServer()` without the `requireProjectConfig` flag, so in a project with no `lucee.json` / `.env` port they still fell back to the hardcoded common-port probe (`[8080, 60000, 3000, 8500]`) and could silently attach to a sibling app: `reload` would reset the wrong app's state, and `generate admin` would introspect the wrong schema and scaffold its controller/views into the current project from a sibling's model — wrong-schema output written into the right project. Both now pass `requireProjectConfig = true`; with no project-bound port they throw `Wheels.ServerNotRunning` with a "set 'port' in lucee.json (or PORT in .env), then start with: wheels start" diagnostic instead of proceeding. `generate admin` is gated (rather than left on the read-side fallback alongside `info` / `routes`) precisely because it both reads a schema and writes files into cwd, so a wrong-server attach is a correctness bug, not just a wrong read. Covered by new server-free specs in `cli/lucli/tests/specs/services/ServerDetectionSpec.cfc` that drive `reload()` and `generateAdmin()` in a no-config project and assert the guard refuses to attach (#2878)
- `wheels migrate` (and its sibling write-side runners — `seed`, `migrate forget` / `pretend`, `migrate rename-system-tables`) refuse to attach to a server that isn't bound to the current project. `cli.lucli.Module::detectServerPort()` previously fell back to a hardcoded common-port probe (`[8080, 60000, 3000, 8500]`) after exhausting `lucee.json` and `.env`, so a freshly-scaffolded project with no port config could silently attach to a sibling app's open Lucee instance and run its migrations against the wrong database (the #2876 / #2878 repro: `wheels new app_a` + `wheels start` in `app_a`, then `wheels migrate latest` in `app_b` ran `app_b`'s migrations against `app_a`'s PostgreSQL). `detectServerPort()` now accepts a `requireProjectConfig` flag that skips the common-port fallback, and `$requireRunningServer()` threads it through to every write-side caller. When the flag is set and no project-bound port resolves, the CLI throws `Wheels.ServerNotRunning` with a clear "set 'port' in lucee.json (or PORT in .env), then start with: wheels start" diagnostic instead of proceeding. Read-side commands (`info`, `routes`, `console`, `dbStatus`, `dbVersion`) keep the legacy fallback — they don't mutate anything, and removing it would regress the no-config development experience. Covered by new server-free specs in `cli/lucli/tests/specs/services/ServerDetectionSpec.cfc` that simulate a sibling app on an ephemeral port and assert the write-side guard refuses to attach (#2878)
- `wheels migrate latest` no longer crashes on PostgreSQL (and CockroachDB) when a migration emits an inline foreign-key constraint — e.g. anything `wheels generate scaffold ... --belongsTo=author` produces. `wheels.databaseAdapters.PostgreSQL.PostgreSQLMigrator` was missing the public `addForeignKeyOptions(sql, options)` method that every other adapter implements (`MySQLMigrator`, `SQLiteMigrator`, `MicrosoftSQLServerMigrator`, `OracleMigrator`); `Abstract.createTable()` builds the inline FK clause via `foreignKeys[i].toForeignKeySQL()` → `ForeignKeyDefinition.cfc` → `adapter.addForeignKeyOptions(...)`, so every PostgreSQL FK column threw `Component [wheels.databaseAdapters.PostgreSQL.PostgreSQLMigrator] has no function with name [addForeignKeyOptions]` and aborted the migration. The new implementation mirrors the MySQL signature (`FOREIGN KEY (col) REFERENCES tbl (refCol)`), which PostgreSQL accepts verbatim, and `CockroachDBMigrator` (which extends `PostgreSQLMigrator`) inherits the fix automatically. The reporter's "works on Windows" observation lined up with the `wheels new` SQLite default — only PostgreSQL/CockroachDB targets ever hit the missing method (#2876)
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ wheels packages add <name> --force # overwrite existing
wheels packages update <name> --yes
wheels packages update --all --yes
wheels packages remove <name>
wheels packages registry info # registry source + cache age
wheels packages registry refresh # bust 24h cache
```

Expand Down
46 changes: 34 additions & 12 deletions cli/lucli/Module.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,14 @@ component extends="modules.BaseModule" {
"console", // interactive CFML REPL — not usable over stdio
"start", // dev server lifecycle (stateful)
"stop", // dev server lifecycle (stateful)
"browser" // multi-step browser testing flow
"browser", // multi-step browser testing flow
// $-prefixed internal helpers. Public ONLY so TestCommandSpec can
// unit-test them directly (the cli/CLAUDE.md "public for specs"
// carve-out) — they are not commands and must never surface as MCP
// tools. LuCLI matches these case-insensitively (McpCommand lowercases
// both the entry and the discovered function name).
"$normalizeTestFilter",
"$resolveAppTestDataSource"
];
}

Expand Down Expand Up @@ -277,6 +284,7 @@ component extends="modules.BaseModule" {
help &= " wheels <command> [options]" & nl & nl;
help &= "Getting Started:" & nl;
help &= " new <name> Scaffold a new Wheels application" & nl;
help &= " create app <name> Alias for new — scaffold a new Wheels application" & nl;
help &= " start Start the dev server" & nl;
help &= " stop Stop the dev server" & nl;
help &= " reload Reload the running app" & nl & nl;
Expand All @@ -297,7 +305,7 @@ component extends="modules.BaseModule" {
help &= " validate Validate project structure and configuration" & nl;
help &= " analyze Static analysis of project code" & nl;
help &= " stats Project statistics (lines of code, model counts, etc.)" & nl;
help &= " notes Find TODO / FIXME / HACK / OPTIMIZE comments" & nl & nl;
help &= " notes Find TODO / FIXME / OPTIMIZE comments (--annotations to customize)" & nl & nl;
help &= "Packages & Deployment:" & nl;
help &= " packages Add, update, search Wheels packages (verb is `add`, not `install`)" & nl;
help &= " upgrade Scan for breaking changes before upgrading Wheels (read-only)" & nl;
Expand Down Expand Up @@ -683,7 +691,11 @@ component extends="modules.BaseModule" {
requireProjectConfig = true
);

var password = detectReloadPassword();
// Auto-detect the reload password from .env / config, but let an explicit
// `--password=<value>` override it (parity with `wheels console`). The
// auto-detect default is unchanged when no flag is given.
var reloadOpts = parseConsoleArgs(structuredArgs(arguments));
var password = len(reloadOpts.password) ? reloadOpts.password : detectReloadPassword();

// F5 fix: physically wipe the Lucee compiled-class cache before
// triggering the framework reload. Lucee Express's default
Expand Down Expand Up @@ -1123,14 +1135,24 @@ component extends="modules.BaseModule" {
if (len(variables.projectRoot) && directoryExists(variables.projectRoot & "/vendor/wheels")) {
out("Project: #variables.projectRoot#");

// Detect Wheels version from vendor
var versionFile = variables.projectRoot & "/vendor/wheels/events/onapplicationstart/settings.cfm";
// Detect the framework version from its authoritative manifest,
// vendor/wheels/wheels.json. The historical
// events/onapplicationstart/settings.cfm path stopped carrying the
// version, so this line silently never rendered. We read the project's
// manifest by absolute path (no `wheels` mapping needed) and apply the
// same structural placeholder check as wheels.BuildInfo: an unstamped
// dev checkout (`@build.version@`) reports as 0.0.0-dev rather than
// leaking the raw build token.
var versionFile = variables.projectRoot & "/vendor/wheels/wheels.json";
if (fileExists(versionFile)) {
try {
var vContent = fileRead(versionFile);
var vMatch = reFindNoCase('version[^"]*"([^"]+)"', vContent, 1, true);
if (arrayLen(vMatch.match) > 1) {
out("Wheels: v#vMatch.match[2]#");
var manifest = deserializeJSON(fileRead(versionFile));
if (isStruct(manifest) && structKeyExists(manifest, "version") && len(manifest.version)) {
var fwVersion = manifest.version;
if (left(fwVersion, 7) == "@build." && right(fwVersion, 1) == "@") {
fwVersion = "0.0.0-dev";
}
out("Wheels: v#fwVersion#");
}
} catch (any e) { /* skip */ }
}
Expand Down Expand Up @@ -1228,7 +1250,7 @@ component extends="modules.BaseModule" {
out(' {"mcpServers":{"wheels":{"command":"wheels","args":["mcp","wheels"]}}}');
out("");
out("For OpenCode, Cursor, and other AI IDEs, see:");
out(" docs/command-line-tools/commands/mcp/mcp-configuration-guide.md");
out(" https://guides.wheels.dev/v4-0-0-snapshot/command-line-tools/mcp-integration");
out("");
out("All public commands in this module are auto-discovered as MCP tools.");
out("Tools are prefixed with the module name: wheels_generate, wheels_migrate, etc.");
Expand Down Expand Up @@ -1583,10 +1605,10 @@ component extends="modules.BaseModule" {
out(" /models List all registered models");
out(" /routes List all routes");
out(" /version Show Wheels version");
out(" /ds Show current datasource");
out(" /ds, /datasource Show current datasource");
out(" /reload Reload the application");
out(" /clear Clear the screen");
out(" /exit, /quit Exit the console");
out(" /exit, /quit, /q Exit the console");
out("");
out("Expression Examples:", "bold");
out(' model("User").findAll() Query all users');
Expand Down
2 changes: 1 addition & 1 deletion vendor/wheels/public/mcp/McpServer.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ component output="false" displayName="MCP Server" {
"name": "wheels-mcp-server",
"version": "1.0.0",
"deprecated": true,
"deprecationNotice": "The in-dev-server MCP endpoint at /wheels/mcp is deprecated as of Wheels 4.0. Use the LuCLI stdio MCP server instead: configure your AI IDE with {command: 'wheels', args: ['mcp', 'wheels']} and see docs/command-line-tools/commands/mcp/mcp-configuration-guide.md for details."
"deprecationNotice": "The in-dev-server MCP endpoint at /wheels/mcp is deprecated as of Wheels 4.0. Use the LuCLI stdio MCP server instead: configure your AI IDE with {command: 'wheels', args: ['mcp', 'wheels']} and see https://guides.wheels.dev/v4-0-0-snapshot/command-line-tools/mcp-integration for details."
};

variables.capabilities = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ A bare positional argument is treated as the filter directory — `wheels test m
| Flag | Description |
|---|---|
| `--filter=<dir>` | Substring or path match against spec directories. Narrows the run to a subset (e.g., `models`, `controller`, `browser`). Bare names are auto-prefixed (`models` → `tests.specs.models`). Also accepted as a positional arg. |
| `--directory=<dir>` | Alias for `--filter` (tutorial chapter 7). When both are supplied, `--directory` wins. |
| `--db=<engine>` | Database engine for `--core` matrix runs only. Ignored for app tests (with a warning) — see [below](#testing-against-different-engines). |
| `--reporter=<name>` | `simple` (default, colourful), `json` (raw runner JSON), `tap` (TAP v13 for CI consumers). |
| `--verbose`, `-v` | Print per-spec output instead of the summary line. |
Expand Down
Loading