Skip to content

Commit 28f2de2

Browse files
bpamiriclaude
andauthored
fix(cli): resolve CLI services via module-relative path so packaged module loads (#2873)
* fix(cli): resolve CLI services via module-relative path so packaged module loads The Wheels Snapshots workflow has failed on every develop push since #2861. The build / "Smoke Test Installed Distribution" job dies at Phase 3 (wheels new): could not find component or class with name [cli.lucli.services.ArgSpec] Root cause: #2861 (ArgSpec migration) and #2363 (TestRunner) instantiated CLI services via the absolute FQN `new cli.lucli.services.X()`. That path only resolves against the source-tree layout `cli/lucli/services/`. The module tarball is built with `tar -C cli/lucli .` (release.yml), which flattens the module root so services live at `<module-root>/services/` — there is no `cli/lucli/` tree and no `cli.lucli` mapping in the package. The 17 sibling services already use the module-relative `new services.X()`, which resolves relative to Module.cfc's own directory in BOTH the source tree and the package. fast-test passes because it runs from source (both forms resolve); only the smoke test, which runs the installed distribution, caught the regression. Convert all 8 absolute references (7x ArgSpec, 1x TestRunner) plus the ArgSpec docblock example to the relative form. TestRunner was a latent twin — no CI path exercises `wheels test` against the installed module yet. Verified locally by building the module tarball from this worktree and from pre-fix HEAD, then running `wheels new` against each in an isolated LUCLI_HOME: pre-fix reproduces the exact error; the fix scaffolds the app successfully. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> * docs(cli): add changelog entry for packaged-module service-path fix Documents the regression fixed in 67ab18f under [Unreleased] ### Fixed, per wheels-bot Reviewer A. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 84d88be commit 28f2de2

3 files changed

Lines changed: 10 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ All historical references to "CFWheels" in this changelog have been preserved fo
2222

2323
### Fixed
2424

25+
- CLI services in `Module.cfc` now instantiate via the module-relative path (`new services.X()`) instead of the absolute FQN (`new cli.lucli.services.X()`), so `wheels new` and the other subcommands resolve their service classes when running from the installed distribution. The module tarball is built with `tar -C cli/lucli .`, which flattens the module root so services live at `<module-root>/services/` with no `cli/lucli/` tree and no `cli.lucli` mapping — the absolute form only resolved against the source-tree layout. That split is why the `fast-test` job (which runs from source, where both forms resolve) stayed green while the snapshot smoke test — which installs the built tarball and runs `wheels new` — failed on every `develop` push since #2861 with `could not find component or class with name [cli.lucli.services.ArgSpec]`. All 8 absolute references (7× `ArgSpec`, 1× the latent `TestRunner` call) are converted to the relative form the 17 sibling services already use; the `ArgSpec` docblock example is updated to match so it cannot re-seed the pattern (#2873)
2526
- Oracle `DROP TABLE` / `DROP VIEW` in the migrator now work on Oracle 19c/21c. `wheels.databaseAdapters.Oracle.OracleMigrator::dropTable()` emitted `DROP TABLE IF EXISTS <name> CASCADE CONSTRAINTS` and `dropView()` inherited `DROP VIEW IF EXISTS` from `Abstract`, but Oracle only added the `IF EXISTS` DDL modifier in 23c — on 19c/21c both are a hard parse error (ORA-00933). Because the `remove-table` migration template re-throws on error, `migrate down`, rollbacks, `force`-create, and migrator test re-runs failed outright on pre-23c Oracle. Both helpers now emit the version-agnostic Oracle PL/SQL idiom — `BEGIN EXECUTE IMMEDIATE 'DROP TABLE <name> CASCADE CONSTRAINTS'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;` — which runs the bare DROP and swallows ORA-00942 ("table or view does not exist"), preserving "drop if exists" semantics on every supported Oracle version with no version detection. `$execute` (`vendor/wheels/migrator/Base.cfc`) never splits on `;` and deliberately omits the trailing-semicolon append for Oracle, so the anonymous block reaches the driver intact. Framework-side counterpart to the demo-app test-populate fix in #2864 (#2869)
2627
- `application.wheels.protectedControllerMethods` is now populated at application start from the public method surface of `wheels.Global` plus the `wheels.controller.*` and `wheels.view.*` mixin components, so framework helpers like `env()`, `model()`, `findAll()`, `redirectTo()`, and `linkTo()` can no longer be invoked as controller actions from a URL. The list was previously initialized to an empty string (the orphaned `local.allowedGlobalMethods = "get,set,mapper"` line in `onapplicationstart.cfc` pointed to the intent but never wired it up), so `$callAction()`'s allow-list check was a no-op. Any unauthenticated `GET /<anyController>/env` request reached the global `env()` helper directly and raised `"The parameter [name] to function [env] is required but was not passed in."` as a 500; other helper names dispatched into unintended code paths. Derived from `getMetaData().functions` on each source component (excluding `$`-prefixed internal methods, which are already gated separately), so the list stays in sync with the framework's mixin surface automatically. Reaching one of these names now throws `Wheels.ActionNotAllowed` and falls through to the missing-action / 404 path, matching every other non-existent action. **Migration note:** applications that defined controller actions with the same name as a public framework helper (e.g. `env`, `model`, `redirectTo`) will need to rename those actions — they now return 404 rather than dispatching, since the protection gate at `processing.cfc:132` fires before the `StructKeyExists(this, action)` lookup that would otherwise reach a same-named user action (#2844)
2728

cli/lucli/Module.cfc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ component extends="modules.BaseModule" {
515515
* shorthand for `--mode=generate`.
516516
*/
517517
private struct function parseSeedArgs(required struct coll) {
518-
var parsed = new cli.lucli.services.ArgSpec()
518+
var parsed = new services.ArgSpec()
519519
.option(name = "environment", default = "")
520520
.option(name = "mode", default = "auto")
521521
.flag(name = "generate", default = false)
@@ -879,7 +879,7 @@ component extends="modules.BaseModule" {
879879
* name → error" (GH #2214).
880880
*/
881881
private struct function parseNewArgs(required struct coll) {
882-
var parsed = new cli.lucli.services.ArgSpec()
882+
var parsed = new services.ArgSpec()
883883
.positional(name = "appName")
884884
.option(name = "port", default = 8080, type = "numeric")
885885
.option(name = "datasource", default = "")
@@ -1565,7 +1565,7 @@ component extends="modules.BaseModule" {
15651565
* target so the "not in a project" guard only fires for the bare form.
15661566
*/
15671567
private struct function parseAnalyzeArgs(required struct coll) {
1568-
var parsed = new cli.lucli.services.ArgSpec()
1568+
var parsed = new services.ArgSpec()
15691569
.positional(name = "target", default = "all")
15701570
.parse(arguments.coll);
15711571
return {
@@ -1695,7 +1695,7 @@ component extends="modules.BaseModule" {
16951695
* migration unchanged — ArgSpec only replaced the hand-rolled token split.
16961696
*/
16971697
private struct function parseDestroyArgs(required struct coll) {
1698-
var parsed = new cli.lucli.services.ArgSpec()
1698+
var parsed = new services.ArgSpec()
16991699
.flag(name = "force", default = false)
17001700
.parse(arguments.coll);
17011701

@@ -1846,7 +1846,7 @@ component extends="modules.BaseModule" {
18461846
* so a short flag arrives as a positional arg<n> value.
18471847
*/
18481848
private boolean function parseVerboseFlag(required struct coll) {
1849-
var parsed = new cli.lucli.services.ArgSpec()
1849+
var parsed = new services.ArgSpec()
18501850
.flag(name = "verbose", default = false)
18511851
.parse(arguments.coll);
18521852
if (parsed.verbose) {
@@ -2526,7 +2526,7 @@ component extends="modules.BaseModule" {
25262526
* consumes them directly.
25272527
*/
25282528
private struct function parseNotesArgs(required struct coll) {
2529-
var parsed = new cli.lucli.services.ArgSpec()
2529+
var parsed = new services.ArgSpec()
25302530
.option(name = "annotations", default = "TODO,FIXME,OPTIMIZE")
25312531
.option(name = "custom", default = "")
25322532
.parse(arguments.coll);
@@ -2626,7 +2626,7 @@ component extends="modules.BaseModule" {
26262626
* bare `--to` to to=true and `--to=x` to to=x — either way the key exists).
26272627
*/
26282628
private struct function parseUpgradeArgs(required struct coll) {
2629-
var parsed = new cli.lucli.services.ArgSpec()
2629+
var parsed = new services.ArgSpec()
26302630
.positional(name = "subcommand", default = "")
26312631
.option(name = "to", default = "")
26322632
.parse(arguments.coll);
@@ -4556,7 +4556,7 @@ component extends="modules.BaseModule" {
45564556
var unloadedSpecPaths = [];
45574557
if (len(arguments.testDirectory)) {
45584558
try {
4559-
var runner = new cli.lucli.services.TestRunner(projectRoot = variables.projectRoot);
4559+
var runner = new services.TestRunner(projectRoot = variables.projectRoot);
45604560
var diskCount = runner.countSpecsOnDisk(arguments.testDirectory);
45614561
var loadedCount = (structKeyExists(result, "bundleStats") && isArray(result.bundleStats))
45624562
? arrayLen(result.bundleStats)

cli/lucli/services/ArgSpec.cfc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
* Usage:
2020
*
21-
* var spec = new cli.lucli.services.ArgSpec()
21+
* var spec = new services.ArgSpec()
2222
* .positional(name = "appName", required = true)
2323
* .flag(name = "sqlite", default = true) // --no-sqlite negates
2424
* .flag(name = "routes", default = true)

0 commit comments

Comments
 (0)