Background
#2856 fixed the --no-* flag family (--no-sqlite, --no-routes, --no-test-db, --no-open-browser) by re-emitting --no-<key> for false values in Module.cfc::argsFromCollection(). That is the correct, minimal bug fix for #2855 and it is shipping. This issue tracks the underlying architectural smell it exposed, which was deliberately out of scope for a bug fix.
The smell
LuCLI parses the command line into a structured argCollection map before dispatching to a module. For wheels new myapp --no-sqlite, the module's new() receives:
argumentCollection = { arg1: "myapp", sqlite: "false" }
(--no-<key> is normalized to <key>=false; see LuceeScriptEngine.parseArguments() in the LuCLI runtime, lines ~335–365.)
cli/lucli/Module.cfc then discards that structure: getArgs() → argsFromCollection() flattens the map back into a flat argv string array (["myapp", "--no-sqlite"]), and each command re-parses it with a hand-rolled token loop:
} else if (arg == "--no-sqlite") {
options.noSQLite = true;
}
So the data flows structured → flattened → re-parsed. getArgs(arguments) is used by ~18 commands.
Why it matters
Proposed direction
Have commands consume argCollection directly (named-arg access: coll.sqlite, coll.arg1) through a small shared typed-arg helper, instead of round-tripping through argv. This removes both the flatten/re-parse layer and the lossy false handling in one move. Large blast radius (~18 commands), hence tracked separately rather than bolted onto the #2856 bug fix.
Related: close the test-robustness gap (smaller, near-term — independent of the refactor)
#2856's unit test hand-builds {sqlite:"false"}, so it validates the re-emit logic but bypasses LuCLI entirely — it cannot catch an upstream LuCLI change to --no-* handling. This is the exact assumption that propagated unverified through triage and both bot reviews. Two cheap improvements:
Refs #2855, #2856.
Background
#2856 fixed the
--no-*flag family (--no-sqlite,--no-routes,--no-test-db,--no-open-browser) by re-emitting--no-<key>forfalsevalues inModule.cfc::argsFromCollection(). That is the correct, minimal bug fix for #2855 and it is shipping. This issue tracks the underlying architectural smell it exposed, which was deliberately out of scope for a bug fix.The smell
LuCLI parses the command line into a structured
argCollectionmap before dispatching to a module. Forwheels new myapp --no-sqlite, the module'snew()receives:(
--no-<key>is normalized to<key>=false; seeLuceeScriptEngine.parseArguments()in the LuCLI runtime, lines ~335–365.)cli/lucli/Module.cfcthen discards that structure:getArgs()→argsFromCollection()flattens the map back into a flat argv string array (["myapp", "--no-sqlite"]), and each command re-parses it with a hand-rolled token loop:} else if (arg == "--no-sqlite") { options.noSQLite = true; }So the data flows structured → flattened → re-parsed.
getArgs(arguments)is used by ~18 commands.Why it matters
argsFromCollection) silently dropped everyfalsevalue, so--no-*flags never survived the round-trip. fix(cli): re-emit --no-* flags so LuCLI-converted negations reach command parsers #2856 re-adds them, but the round-trip itself is the fragile part.--no-<key>for anyfalsevalue cannot distinguish a genuine--no-flagnegation from an explicit--flag=falseliteral (both reach the module askey=false). Benign for today's boolean flags, but a latent trap for any future--flag=valueoption whose value can befalse. (Flagged in the fix(cli): re-emit --no-* flags so LuCLI-converted negations reach command parsers #2856 review.)Proposed direction
Have commands consume
argCollectiondirectly (named-arg access:coll.sqlite,coll.arg1) through a small shared typed-arg helper, instead of round-tripping through argv. This removes both the flatten/re-parse layer and the lossyfalsehandling in one move. Large blast radius (~18 commands), hence tracked separately rather than bolted onto the #2856 bug fix.Related: close the test-robustness gap (smaller, near-term — independent of the refactor)
#2856's unit test hand-builds
{sqlite:"false"}, so it validates the re-emit logic but bypasses LuCLI entirely — it cannot catch an upstream LuCLI change to--no-*handling. This is the exact assumption that propagated unverified through triage and both bot reviews. Two cheap improvements:tools/test-onboarding.sh:wheels new <app> --no-sqlite→ nodb/*.sqlitefiles, andlucee.jsonconfiguration.datasources=={}.argsFromCollection()pinning the LuCLIkey=falsecontract it depends on (LuceeScriptEngine.parseArguments()), so the cross-runtime dependency is discoverable by the next maintainer.Refs #2855, #2856.