From b728959f0fe2a6c8549cd321cf297694b10e86a0 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 04:50:35 -0700 Subject: [PATCH 1/3] fix(run): don't print the built-in variables setup for .cfm templates executeTemplate prepended the built-in variables setup (script) to the .cfm content as plain text. In a template, text outside is output, so 'lucli run hello.cfm' printed ~20 lines of setup code and an 'Original Script Content' marker before 'hello 2'. The setup now runs inside directly ahead of the unchanged template (wrapBuiltinSetupForTemplate), so the output is exactly the template's and the built-in variables stay available. .cfs scripts go through executeScriptContent and are unchanged. Upstream: cybersonic/LuCLI#137 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- CHANGELOG.md | 2 ++ FORK.md | 1 + .../org/lucee/lucli/LuceeScriptEngine.java | 29 ++++++++++++++-- .../org/lucee/lucli/TemplatePreludeTest.java | 34 +++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/lucee/lucli/TemplatePreludeTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6c567..9e06226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## Unreleased +- **Fix: `lucli run .cfm` No Longer Prints the Built-in Variables Setup:** For a `.cfm` (a template, where text outside `` is output), the built-in variables setup was prepended as plain text, so about 20 lines of setup code and an `// === Original Script Content ===` marker were printed before the template's output. The setup now runs inside `` ahead of the unchanged template, so the output is exactly what the template renders, and the built-in variables (`__scriptPath`, `ARGS`, …) are still available. `.cfs` scripts were not affected. Upstream: cybersonic/LuCLI#137. Adds `TemplatePreludeTest`. + - **Feature: Modules Can Tell an MCP `tools/call` From a Terminal Call:** During `tools/call`, the module function now receives a reserved `__lucliMcpCall=true` argument, so a module can apply a different policy to MCP clients (for example refusing a command meant for a person at the terminal). The argument is runtime-owned: any argument of that name (any case, with or without `--`) supplied by an MCP client or on the command line is removed before the module runs, so it can't be forged or suppressed, and a terminal invocation never carries it. The positional binder never fills a declared parameter of that name from a terminal `argN`. Documented in the MCP module servers guide. Adds coverage in `McpCommandTest` (`toolsCallCarriesARuntimeOwnedMcpMarker`, `terminalInvocationNeverCarriesTheMcpMarker`, `declaredMarkerParameterIsNeverBoundFromATerminalPositional`). - **Change: Dev Servers Listen on `127.0.0.1` by Default:** Servers used to listen on every network interface (Tomcat connectors had no `address`, Jetty no `jetty.http.host`, Docker published the port on all host interfaces), so a `lucli server start` was reachable from the whole local network. A new `lucee.json` key `bindAddress` (default `127.0.0.1`) now sets the HTTP and HTTPS connectors' `address` and that of every other connector (AJP, Nio2, APR, ...) that server.xml leaves without one, Jetty's `jetty.http.host`, the host side of Docker's `-p`, and JMX monitoring (`jmxremote.host` plus a matching RMI hostname and port). Unauthenticated JMX is refused on a non-loopback bind unless authentication is configured in `jvm.additionalArgs`, and the schema's documented `monitoring.enabled` default is corrected to `false`. Opt in to other interfaces with `"bindAddress": "0.0.0.0"`, `server start|run --host 0.0.0.0`, or the one-shot `bindAddress=0.0.0.0` override; the startup output shows the listen address and warns when it is all interfaces. `host` keeps its meaning (URL and certificate hostname). Adds coverage in `TomcatServerXmlPatcherTest`, `DockerRuntimeProviderTest`, `JettyBaseConfigGeneratorTest`, `LuceeServerConfigTest` and `ServerConfigHelperTest`. diff --git a/FORK.md b/FORK.md index 4336b50..bb020c2 100644 --- a/FORK.md +++ b/FORK.md @@ -83,6 +83,7 @@ No release is tagged for this queue yet; tagging needs the Wheels release owner' | Order | Change | Purpose | |---|---|---| | 8 | wheels-dev/LuCLI [#19](https://github.com/wheels-dev/LuCLI/pull/19) (issue [#17](https://github.com/wheels-dev/LuCLI/issues/17)) | A module function run by an MCP `tools/call` receives the runtime-owned `__lucliMcpCall=true` argument; any caller-supplied argument of that name is dropped, and a terminal call never carries it | +| 13 | wheels-dev/LuCLI PR (this change; issue: upstream [cybersonic/LuCLI#137](https://github.com/cybersonic/LuCLI/issues/137)) | `lucli run .cfm` runs the built-in variables setup inside `` instead of printing it | Upstream (row 8): issue [cybersonic/LuCLI#133](https://github.com/cybersonic/LuCLI/issues/133), PR [cybersonic/LuCLI#134](https://github.com/cybersonic/LuCLI/pull/134) (a `cherry-pick -x` of diff --git a/src/main/java/org/lucee/lucli/LuceeScriptEngine.java b/src/main/java/org/lucee/lucli/LuceeScriptEngine.java index 0c21b60..a4c01b8 100644 --- a/src/main/java/org/lucee/lucli/LuceeScriptEngine.java +++ b/src/main/java/org/lucee/lucli/LuceeScriptEngine.java @@ -660,9 +660,20 @@ private void executeTemplate(String scriptFile, String scriptContent, String[] s StringOutput.getInstance().println("${EMOJI_INFO} Arguments: " + Arrays.toString(scriptArgs)); } - // For CFS scripts, inject built-in variables directly into the script content - // so they are available as CFML variables, not just engine bindings - String scriptWithVariables = injectBuiltinVariables(scriptContent, scriptFile, scriptArgs); + // A .cfm is a template: text outside is output. The built-in + // variables setup is script, so it must run inside ; prepending it + // as plain text printed it before the template's output (upstream + // cybersonic/LuCLI#137). + String scriptWithVariables = scriptContent; + try { + BuiltinVariableManager variableManager = BuiltinVariableManager.getInstance(isVerboseMode(), isDebugMode()); + scriptWithVariables = wrapBuiltinSetupForTemplate( + variableManager.createVariableSetupScript(scriptFile, scriptArgs), scriptContent); + } catch (IOException e) { + if (isDebugMode()) { + System.err.println("Warning: Failed to inject built-in variables: " + e.getMessage()); + } + } if (isVerboseMode() || isDebugMode()) { System.out.println("=== CFS Script with Built-in Variables ==="); @@ -676,6 +687,18 @@ private void executeTemplate(String scriptFile, String scriptContent, String[] s } + /** + * Prefix a template (.cfm) with the built-in variables setup, run as script: + * {@code setup} then the template unchanged. No other text is + * added before the template, so its output is exactly what it renders. + */ + static String wrapBuiltinSetupForTemplate(String variableSetup, String templateContent) { + if (variableSetup == null || variableSetup.isBlank()) { + return templateContent; + } + return "\n" + variableSetup + "\n" + templateContent; + } + private void executeWrappedScript(String scriptContent, String scriptFile, String[] scriptArgs) throws Exception { // Try internal component loading first, fall back to filesystem extraction if needed diff --git a/src/test/java/org/lucee/lucli/TemplatePreludeTest.java b/src/test/java/org/lucee/lucli/TemplatePreludeTest.java new file mode 100644 index 0000000..976fb89 --- /dev/null +++ b/src/test/java/org/lucee/lucli/TemplatePreludeTest.java @@ -0,0 +1,34 @@ +package org.lucee.lucli; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * A .cfm run by `lucli run` gets the built-in variables setup inside , so the + * setup runs instead of being printed before the template's output + * (upstream cybersonic/LuCLI#137). + */ +class TemplatePreludeTest { + + @Test + void setupRunsInsideCfscriptAndTheTemplateFollowsUnchanged() { + String setup = "// LuCLI Built-in Variables Setup\n__scriptDir = '/tmp';"; + String template = "hello #1+1#"; + String wrapped = LuceeScriptEngine.wrapBuiltinSetupForTemplate(setup, template); + + assertTrue(wrapped.startsWith("\n" + setup + "\n")); + assertTrue(wrapped.endsWith(template)); + assertEquals(wrapped.indexOf("") + "".length(), wrapped.indexOf(template), + "nothing may sit between the setup and the template, or it is output"); + assertFalse(wrapped.contains("Original Script Content")); + } + + @Test + void emptySetupLeavesTheTemplateAsIs() { + assertEquals("

x

", LuceeScriptEngine.wrapBuiltinSetupForTemplate("", "

x

")); + assertEquals("

x

", LuceeScriptEngine.wrapBuiltinSetupForTemplate(null, "

x

")); + } +} From c26936d66e5b781b7f27aebc655c7fb6993c4b96 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 04:50:56 -0700 Subject: [PATCH 2/3] docs(fork): link row 13 to #26 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- FORK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FORK.md b/FORK.md index bb020c2..6aba6d2 100644 --- a/FORK.md +++ b/FORK.md @@ -83,7 +83,7 @@ No release is tagged for this queue yet; tagging needs the Wheels release owner' | Order | Change | Purpose | |---|---|---| | 8 | wheels-dev/LuCLI [#19](https://github.com/wheels-dev/LuCLI/pull/19) (issue [#17](https://github.com/wheels-dev/LuCLI/issues/17)) | A module function run by an MCP `tools/call` receives the runtime-owned `__lucliMcpCall=true` argument; any caller-supplied argument of that name is dropped, and a terminal call never carries it | -| 13 | wheels-dev/LuCLI PR (this change; issue: upstream [cybersonic/LuCLI#137](https://github.com/cybersonic/LuCLI/issues/137)) | `lucli run .cfm` runs the built-in variables setup inside `` instead of printing it | +| 13 | wheels-dev/LuCLI [#26](https://github.com/wheels-dev/LuCLI/pull/26) (issue: upstream [cybersonic/LuCLI#137](https://github.com/cybersonic/LuCLI/issues/137)) | `lucli run .cfm` runs the built-in variables setup inside `` instead of printing it | Upstream (row 8): issue [cybersonic/LuCLI#133](https://github.com/cybersonic/LuCLI/issues/133), PR [cybersonic/LuCLI#134](https://github.com/cybersonic/LuCLI/pull/134) (a `cherry-pick -x` of From 2ce882e5ae98a55889b199ea44911d2e0524d35a Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 05:05:01 -0700 Subject: [PATCH 3/3] fix(run): keep the injectBuiltinVariables timing span for .cfm; label the debug dump as a CFM template Review nits on #26. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- src/main/java/org/lucee/lucli/LuceeScriptEngine.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lucee/lucli/LuceeScriptEngine.java b/src/main/java/org/lucee/lucli/LuceeScriptEngine.java index a4c01b8..560efc7 100644 --- a/src/main/java/org/lucee/lucli/LuceeScriptEngine.java +++ b/src/main/java/org/lucee/lucli/LuceeScriptEngine.java @@ -665,6 +665,7 @@ private void executeTemplate(String scriptFile, String scriptContent, String[] s // as plain text printed it before the template's output (upstream // cybersonic/LuCLI#137). String scriptWithVariables = scriptContent; + Timer.start("injectBuiltinVariables"); try { BuiltinVariableManager variableManager = BuiltinVariableManager.getInstance(isVerboseMode(), isDebugMode()); scriptWithVariables = wrapBuiltinSetupForTemplate( @@ -673,10 +674,12 @@ private void executeTemplate(String scriptFile, String scriptContent, String[] s if (isDebugMode()) { System.err.println("Warning: Failed to inject built-in variables: " + e.getMessage()); } + } finally { + Timer.stop("injectBuiltinVariables"); } if (isVerboseMode() || isDebugMode()) { - System.out.println("=== CFS Script with Built-in Variables ==="); + System.out.println("=== CFM Template with Built-in Variables ==="); System.out.println(scriptWithVariables); System.out.println("=== End Script ==="); }