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: Test Suite No Longer Writes Into the Developer's LuCLI Home:** `ModuleArgBindingTest` and `McpCommandTest` installed their fixture modules into the real `~/.lucli/modules` and spawned `dev-lucli.sh` without passing a home, so `mvn test` compiled the fixtures into the real `~/.lucli/lucee-server`, and with an isolated test home (`-Duser.home=<empty dir>`) 17 tests failed because the child couldn't find the fixtures. Each class now uses a temporary LuCLI home (`@TempDir`), installs its fixture there and passes it to the child as `LUCLI_HOME`. Issue: #25 (upstream: cybersonic/LuCLI#144). Adds `ModuleArgBindingTest.childRunsInTheTemporaryLucliHome`.

- **Fix: Hints Name the Binary You Ran:** User-facing hints such as "Use 'lucli server new' …", "Run 'lucli secrets init' …" or "run 'lucli modules list' …" always said `lucli`, also under an aliased binary (e.g. `wheels`). They now use `LuCLI.cliName()`, the active profile's name. The bundled `lang` module's "Use 'lucli lang help'" hint does the same. picocli `@Command` help text (annotation constants) is unchanged. Adds `CliNameTest`.

- **Fix: The Bundled `lang` Module Compiles:** eight help strings in `modules/lang/Module.cfc` contained an unescaped `#` (`# e.g., …`), which CFML reads as an expression delimiter, so the module failed to compile ("Invalid identifier") and `lang` was unusable. They are escaped as `##`. Upstream: cybersonic/LuCLI#146 (PR cybersonic/LuCLI#147).
Expand Down
1 change: 1 addition & 0 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ No release is tagged for this queue yet; tagging needs the Wheels release owner'
| 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 |
| 14 | wheels-dev/LuCLI [#27](https://github.com/wheels-dev/LuCLI/pull/27) (fork-only polish, no upstream issue) | User-facing hints name the active binary (`wheels server new`, not `lucli server new`) via `LuCLI.cliName()` |
| 15 | wheels-dev/LuCLI [#28](https://github.com/wheels-dev/LuCLI/pull/28) (issue: fork [#25](https://github.com/wheels-dev/LuCLI/issues/25), upstream [cybersonic/LuCLI#144](https://github.com/cybersonic/LuCLI/issues/144)) | Test suite isolated from the developer's LuCLI home: `ModuleArgBindingTest` / `McpCommandTest` use a temporary `LUCLI_HOME` passed to the `dev-lucli.sh` child |

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
29 changes: 23 additions & 6 deletions src/test/java/org/lucee/lucli/cli/commands/McpCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.lucee.lucli.paths.LucliPaths;
import org.junit.jupiter.api.io.TempDir;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand All @@ -34,7 +34,7 @@
* Integration tests for {@link McpCommand}.
*
* Copies a small fixture module (src/test/resources/mcp-fixture-module) into
* the real LuCLI modules directory, spawns `dev-lucli.sh mcp mcpfixture` as a
* a temporary LuCLI home, spawns `dev-lucli.sh mcp mcpfixture` with that home as a
* subprocess, and asserts on the JSON-RPC responses emitted on stdout.
*
* The fixture is removed in {@link #tearDown()}.
Expand All @@ -51,6 +51,12 @@ class McpCommandTest {
private static Path fixtureInstalledPath;
private static String lucliBin;

// The LuCLI home for this class: the fixture is installed here and the
// dev-lucli.sh child gets it as LUCLI_HOME, so the suite never touches the
// developer's own home and passes in a clean one.
@TempDir
static Path lucliHome;

@BeforeAll
static void setUp() throws Exception {
// Subprocess-based tests need bash + dev-lucli.sh. Windows runners
Expand All @@ -65,7 +71,7 @@ static void setUp() throws Exception {
assertTrue(Files.isDirectory(fixtureSrc),
"fixture source not found at " + fixtureSrc);

Path modulesDir = LucliPaths.resolve().modulesDir();
Path modulesDir = lucliHome.resolve("modules");
Files.createDirectories(modulesDir);
fixtureInstalledPath = modulesDir.resolve(FIXTURE_MODULE_NAME);

Expand Down Expand Up @@ -312,7 +318,7 @@ void terminalInvocationNeverCarriesTheMcpMarker() throws Exception {
for (String[] invocation : invocations) {
List<String> cmd = new ArrayList<>(List.of("/bin/bash", lucliBin, FIXTURE_MODULE_NAME));
cmd.addAll(List.of(invocation));
ProcessBuilder pb = new ProcessBuilder(cmd);
ProcessBuilder pb = lucliProcess(cmd);
pb.redirectErrorStream(true);
Process proc = pb.start();
String output = new String(proc.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
Expand All @@ -324,7 +330,7 @@ void terminalInvocationNeverCarriesTheMcpMarker() throws Exception {
@Test
void declaredMarkerParameterIsNeverBoundFromATerminalPositional() throws Exception {
// The fixture declares `__LuCliMcpCall` (a case variant): a positional must not fill it.
ProcessBuilder pb = new ProcessBuilder("/bin/bash", lucliBin, FIXTURE_MODULE_NAME, "declaredmarker", "yes");
ProcessBuilder pb = lucliProcess("/bin/bash", lucliBin, FIXTURE_MODULE_NAME, "declaredmarker", "yes");
pb.redirectErrorStream(true);
Process proc = pb.start();
String output = new String(proc.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
Expand All @@ -345,7 +351,7 @@ void declaredMarkerParameterIsNeverBoundFromATerminalPositional() throws Excepti
// Send the given JSON-RPC lines to `dev-lucli.sh mcp mcpfixture` as a
// subprocess. Returns each parsed response as a JsonNode.
private List<JsonNode> runMcpSession(List<String> requests) throws Exception {
ProcessBuilder pb = new ProcessBuilder(
ProcessBuilder pb = lucliProcess(
"/bin/bash", lucliBin, "mcp", FIXTURE_MODULE_NAME);
pb.redirectErrorStream(false);
Process proc = pb.start();
Expand Down Expand Up @@ -405,4 +411,15 @@ private static void deleteRecursively(Path path) throws IOException {
}
}


// Every child gets this class's LuCLI home.
private static ProcessBuilder lucliProcess(String... command) {
return lucliProcess(List.of(command));
}

private static ProcessBuilder lucliProcess(List<String> command) {
ProcessBuilder pb = new ProcessBuilder(command);
pb.environment().put("LUCLI_HOME", lucliHome.toString());
return pb;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.lucee.lucli.paths.LucliPaths;
import org.junit.jupiter.api.io.TempDir;

import static org.junit.jupiter.api.Assertions.assertTrue;

Expand All @@ -39,6 +39,12 @@ class ModuleArgBindingTest {
private static Path fixtureInstalledPath;
private static String lucliBin;

// The LuCLI home for this class: the fixture is installed here and the
// dev-lucli.sh child gets it as LUCLI_HOME, so the suite never touches the
// developer's own home and passes in a clean one.
@TempDir
static Path lucliHome;

@BeforeAll
static void setUp() throws Exception {
Assumptions.assumeFalse(
Expand All @@ -50,7 +56,7 @@ static void setUp() throws Exception {
assertTrue(Files.isDirectory(fixtureSrc),
"fixture source not found at " + fixtureSrc);

Path modulesDir = LucliPaths.resolve().modulesDir();
Path modulesDir = lucliHome.resolve("modules");
Files.createDirectories(modulesDir);
fixtureInstalledPath = modulesDir.resolve(FIXTURE_MODULE_NAME);

Expand Down Expand Up @@ -141,13 +147,21 @@ void clusteredRootFlagsAndAttachedValuesPreserveModuleTimeout() throws Exception
}
}

@Test
void childRunsInTheTemporaryLucliHome() throws Exception {
String output = runCli("typedbind", "report", "hello");
assertTrue(Files.isDirectory(lucliHome.resolve("lucee-server")),
"the child must create its Lucee context under LUCLI_HOME, not the developer's home");
assertTrue(output.contains("first=hello"), output);
}

private String runCli(String... args) throws IOException, InterruptedException {
List<String> cmd = new ArrayList<>();
cmd.add("/bin/bash");
cmd.add(lucliBin);
for (String a : args) cmd.add(a);

ProcessBuilder pb = new ProcessBuilder(cmd);
ProcessBuilder pb = lucliProcess(cmd);
pb.redirectErrorStream(true);
Process proc = pb.start();

Expand Down Expand Up @@ -179,4 +193,11 @@ private static void deleteRecursively(Path path) throws IOException {
.forEach(p -> { try { Files.delete(p); } catch (IOException e) { throw new RuntimeException(e); } });
}
}

// Every child gets this class's LuCLI home.
private static ProcessBuilder lucliProcess(List<String> command) {
ProcessBuilder pb = new ProcessBuilder(command);
pb.environment().put("LUCLI_HOME", lucliHome.toString());
return pb;
}
}
Loading