From 8da3234cf4eb288699648ed93c97229cabcb3f50 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 04:38:08 -0700 Subject: [PATCH 1/2] fix(server): start --dry-run writes nothing and previews --name In a folder without lucee.json, 'server start --dry-run' saved a default lucee.json into the project (LuceeServerConfig.loadConfig persists the default), and the preview showed the folder name although --name was given (the name is only applied by the real start). loadConfig gains a persistDefault flag (the existing signatures keep persisting); the start handler passes !dryRun, and the dry-run preview applies --name as startServerInternal does. Upstream: cybersonic/LuCLI#139 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- CHANGELOG.md | 2 + FORK.md | 1 + .../lucee/lucli/server/LuceeServerConfig.java | 14 ++++++- .../lucli/server/ServerCommandHandler.java | 8 +++- .../server/ServerCommandHandlerTest.java | 37 +++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6c567..1d885cc 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: `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. + - **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..b9cbce9 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 | +| 11 | wheels-dev/LuCLI PR (this change; 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` | 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/server/LuceeServerConfig.java b/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java index e1f5ab1..764d4a9 100644 --- a/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java +++ b/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java @@ -338,6 +338,16 @@ public static ServerConfig loadConfig(Path projectDir) throws IOException { * @param configFileName The configuration file name (e.g., "lucee.json", "lucee-simple.json") */ public static ServerConfig loadConfig(Path projectDir, String configFileName) throws IOException { + return loadConfig(projectDir, configFileName, true); + } + + /** + * Load configuration from a specified file in the directory. + * @param persistDefault when the file doesn't exist, whether to write the default + * configuration to it. A preview ({@code server start --dry-run}) passes false so + * it never writes to the project (upstream cybersonic/LuCLI#139). + */ + public static ServerConfig loadConfig(Path projectDir, String configFileName, boolean persistDefault) throws IOException { // Reset any previously loaded .env variables for this new project/config load clearLoadedEnvFileVariables(); clearRealizedEnvVariables(); @@ -347,7 +357,9 @@ public static ServerConfig loadConfig(Path projectDir, String configFileName) th if (!Files.exists(configFile)) { // Create default configuration ServerConfig defaultConfig = createDefaultConfig(projectDir); - saveConfig(defaultConfig, configFile); + if (persistDefault) { + saveConfig(defaultConfig, configFile); + } return defaultConfig; } diff --git a/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java b/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java index a29f764..39eac2a 100644 --- a/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java +++ b/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java @@ -332,7 +332,8 @@ private String handleServerStart(LuceeServerManager serverManager, String[] args String cfgFile = configFileName != null ? configFileName : "lucee.json"; Path cfgPath = projectDir.resolve(cfgFile); boolean configFileExisted = Files.exists(cfgPath); - LuceeServerConfig.ServerConfig finalConfig = LuceeServerConfig.loadConfig(projectDir, cfgFile); + // A dry run is a preview: it must not write a default lucee.json into the project. + LuceeServerConfig.ServerConfig finalConfig = LuceeServerConfig.loadConfig(projectDir, cfgFile, !dryRun); // Apply environment overrides if --env flag was provided if (environment != null && !environment.trim().isEmpty()) { @@ -413,6 +414,11 @@ private String handleServerStart(LuceeServerManager serverManager, String[] args // If dry-run, show what would happen and exit if (dryRun) { + // --name applies to the started server (LuceeServerManager.startServerInternal); + // show it in the preview too. + if (customName != null && !customName.trim().isEmpty()) { + finalConfig.name = customName.trim(); + } StringBuilder result = new StringBuilder(); result.append("📋 DRY RUN: Server configuration that would be used:\n\n"); if (includeSelection.includeConfig) { diff --git a/src/test/java/org/lucee/lucli/server/ServerCommandHandlerTest.java b/src/test/java/org/lucee/lucli/server/ServerCommandHandlerTest.java index 9f0176e..52356ef 100644 --- a/src/test/java/org/lucee/lucli/server/ServerCommandHandlerTest.java +++ b/src/test/java/org/lucee/lucli/server/ServerCommandHandlerTest.java @@ -23,6 +23,43 @@ class ServerCommandHandlerTest { private static final ObjectMapper MAPPER = new ObjectMapper(); + // upstream cybersonic/LuCLI#139: a dry run is a preview and must not write to the project + @Test + void serverStartDryRun_withoutLuceeJson_writesNothing() throws Exception { + Files.writeString(tempDir.resolve("index.cfm"), "hi"); + + ServerCommandHandler handler = new ServerCommandHandler(true, tempDir); + String output = handler.executeCommand("server", new String[] { + "start", "--dry-run", "--port", "63301" + }); + + assertNotNull(output); + assertTrue(output.contains("DRY RUN"), "a preview is still printed"); + assertFalse(Files.exists(tempDir.resolve("lucee.json")), "start --dry-run must not create lucee.json"); + } + + // upstream cybersonic/LuCLI#139: --name applies to the started server, so the preview shows it + @Test + void serverStartDryRun_previewShowsTheNameOverride() throws Exception { + Files.writeString(tempDir.resolve("lucee.json"), """ + { + "name": "from-config", + "port": 8080 + } + """); + + ServerCommandHandler handler = new ServerCommandHandler(true, tempDir); + String output = handler.executeCommand("server", new String[] { + "start", "--dry-run", "--name", "foo" + }); + + assertNotNull(output); + assertTrue(output.contains("\"name\" : \"foo\"") || output.contains("\"name\": \"foo\""), + "Dry-run output should show the --name override"); + assertTrue(Files.readString(tempDir.resolve("lucee.json")).contains("from-config"), + "lucee.json is not modified"); + } + @Test void serverStartDryRun_portOverrideDoesNotMutateLuceeJson() throws Exception { Path configFile = tempDir.resolve("lucee.json"); From b2ff00320d92048a972af439b7d79a28e01c63b7 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 04:38:17 -0700 Subject: [PATCH 2/2] docs(fork): link row 11 to #23 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 b9cbce9..a06638f 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 | -| 11 | wheels-dev/LuCLI PR (this change; 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` | +| 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` | 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