Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
All notable changes to this project will be documented in this file.

## Unreleased
- **Fix: `lucli run <file>.cfm` No Longer Prints the Built-in Variables Setup:** For a `.cfm` (a template, where text outside `<cfscript>` 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 `<cfscript>` 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`.

- **Fix: Root Flags Before a Root Subcommand Under an Aliased Binary:** `wheels --timing cfml 'x'` (any root option such as `--timing`, `--verbose`, `-e prod`, `--timeout=30` before a reserved root subcommand) was handed to the module, because the reserved-token rule treated the subcommand as a module positional when it wasn't at index 1. The leading position now skips root options and their separate values, so the root subcommand runs with the flags as it does without them. Reserved tokens later in a module command are still rewritten as before. Adds `LuCLIModuleHelpPreprocessTest` cases.

- **Fix: `server start --dry-run` Writes Nothing and Shows `--name`:** In a folder without `lucee.json`, the dry run wrote a default `lucee.json` into the project, and its preview showed the folder name even when `--name` was given. A dry run now loads the configuration without persisting the default (`LuceeServerConfig.loadConfig(projectDir, file, persistDefault)`), and the preview applies `--name` as a real start does. Upstream: cybersonic/LuCLI#139. Adds two `ServerCommandHandlerTest` cases.
Expand Down
1 change: 1 addition & 0 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ No release is tagged for this queue yet; tagging needs the Wheels release owner'
| 10 | wheels-dev/LuCLI [#22](https://github.com/wheels-dev/LuCLI/pull/22) (issue: upstream [cybersonic/LuCLI#140](https://github.com/cybersonic/LuCLI/issues/140)) | REPL / terminal history and `settings.json` follow the active LuCLI home instead of `user.home`; legacy `~/.lucli/settings.json` read once and copied |
| 11 | wheels-dev/LuCLI [#23](https://github.com/wheels-dev/LuCLI/pull/23) (issue: upstream [cybersonic/LuCLI#139](https://github.com/cybersonic/LuCLI/issues/139)) | `server start --dry-run` no longer writes a default `lucee.json`; the preview shows `--name` |
| 12 | wheels-dev/LuCLI [#24](https://github.com/wheels-dev/LuCLI/pull/24) (fork-only: follows row 2 / upstream PR [cybersonic/LuCLI#124](https://github.com/cybersonic/LuCLI/pull/124)) | Root options before a reserved root subcommand under an aliased binary run that subcommand instead of the module (`wheels --timing cfml 'x'`) |
| 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 <file>.cfm` runs the built-in variables setup inside `<cfscript>` 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
Expand Down
34 changes: 30 additions & 4 deletions src/main/java/org/lucee/lucli/LuceeScriptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -660,12 +660,26 @@ 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 <cfscript> is output. The built-in
// variables setup is script, so it must run inside <cfscript>; prepending it
// 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(
variableManager.createVariableSetupScript(scriptFile, scriptArgs), scriptContent);
} catch (IOException e) {
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 ===");
}
Expand All @@ -676,6 +690,18 @@ private void executeTemplate(String scriptFile, String scriptContent, String[] s
}


/**
* Prefix a template (.cfm) with the built-in variables setup, run as script:
* {@code <cfscript>setup</cfscript>} 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 "<cfscript>\n" + variableSetup + "\n</cfscript>" + templateContent;
}

private void executeWrappedScript(String scriptContent, String scriptFile, String[] scriptArgs) throws Exception {

// Try internal component loading first, fall back to filesystem extraction if needed
Expand Down
34 changes: 34 additions & 0 deletions src/test/java/org/lucee/lucli/TemplatePreludeTest.java
Original file line number Diff line number Diff line change
@@ -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 <cfscript>, 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 = "<cfoutput>hello #1+1#</cfoutput>";
String wrapped = LuceeScriptEngine.wrapBuiltinSetupForTemplate(setup, template);

assertTrue(wrapped.startsWith("<cfscript>\n" + setup + "\n</cfscript>"));
assertTrue(wrapped.endsWith(template));
assertEquals(wrapped.indexOf("</cfscript>") + "</cfscript>".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("<p>x</p>", LuceeScriptEngine.wrapBuiltinSetupForTemplate("", "<p>x</p>"));
assertEquals("<p>x</p>", LuceeScriptEngine.wrapBuiltinSetupForTemplate(null, "<p>x</p>"));
}
}
Loading