Skip to content

Commit ddcd713

Browse files
feat: add --no-ui flag to serve command to disable web UI at launch
When --no-ui is passed to `code-iq serve`, the React SPA is excluded and only the REST API and MCP server remain active. Useful for headless or CI environments. - CodeIqApplication.main() detects --no-ui before Spring context init and sets codeiq.ui.enabled=false as a system property - SpaController gains @ConditionalOnProperty so it is only registered when codeiq.ui.enabled=true (default: true / matchIfMissing=true) - CodeIqConfig gains uiEnabled field bound to codeiq.ui.enabled - application.yml sets codeiq.ui.enabled: true as default - ServeCommand: added --no-ui option; startup log now states whether Web UI is enabled or disabled - Tests: 2 new --no-ui flag tests in ServeCommandTest, 5 new SpaControllerConditionalTest tests (1447 total, 0 failures) Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 parent 482ca24 commit ddcd713

7 files changed

Lines changed: 103 additions & 1 deletion

File tree

src/main/java/io/github/randomcodespace/iq/CodeIqApplication.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ public static void main(String[] args) {
6767
System.setProperty("server.port", portStr);
6868
}
6969

70+
// Disable web UI if --no-ui flag is present
71+
boolean noUi = Arrays.asList(args).contains("--no-ui");
72+
if (noUi) {
73+
System.setProperty("codeiq.ui.enabled", "false");
74+
}
75+
7076
// Resolve codebase root so Neo4j points to the correct graph.db
7177
String codebasePath = extractPositionalArg(args, "serve");
7278
java.nio.file.Path root = java.nio.file.Path.of(

src/main/java/io/github/randomcodespace/iq/cli/ServeCommand.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public class ServeCommand implements Callable<Integer> {
4242
@Option(names = {"--graph"}, description = "Path to shared graph directory (overrides default)")
4343
private Path graphPath;
4444

45+
@Option(names = {"--no-ui"}, defaultValue = "false",
46+
description = "Disable the web UI (React SPA). API and MCP endpoints remain active.")
47+
private boolean noUi;
48+
4549
@Autowired
4650
private CodeIqConfig config;
4751

@@ -73,7 +77,11 @@ public Integer call() {
7377

7478
CliOutput.step("\uD83D\uDE80", "@|bold,green Server started|@");
7579
System.out.println();
76-
CliOutput.info(" URL: http://" + host + ":" + port);
80+
if (noUi) {
81+
CliOutput.info(" Web UI: disabled (API and MCP active at :" + port + ")");
82+
} else {
83+
CliOutput.info(" Web UI: http://" + host + ":" + port + " (React SPA)");
84+
}
7785
CliOutput.info(" REST API: http://" + host + ":" + port + "/api");
7886
CliOutput.info(" MCP: http://" + host + ":" + port + "/mcp");
7987
CliOutput.info(" Health: http://" + host + ":" + port + "/actuator/health");
@@ -94,4 +102,5 @@ public Integer call() {
94102
public int getPort() { return port; }
95103
public String getHost() { return host; }
96104
public Path getGraphPath() { return graphPath; }
105+
public boolean isNoUi() { return noUi; }
97106
}

src/main/java/io/github/randomcodespace/iq/config/CodeIqConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public class CodeIqConfig {
2828
/** Graph configuration sub-properties. */
2929
private Graph graph = new Graph();
3030

31+
/** Whether to serve the React web UI. Set to false via --no-ui flag. */
32+
private boolean uiEnabled = true;
33+
3134
public static class Graph {
3235
private String path = ".osscodeiq/graph.db";
3336

@@ -95,4 +98,12 @@ public Graph getGraph() {
9598
public void setGraph(Graph graph) {
9699
this.graph = graph;
97100
}
101+
102+
public boolean isUiEnabled() {
103+
return uiEnabled;
104+
}
105+
106+
public void setUiEnabled(boolean uiEnabled) {
107+
this.uiEnabled = uiEnabled;
108+
}
98109
}

src/main/java/io/github/randomcodespace/iq/web/SpaController.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.randomcodespace.iq.web;
22

3+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
34
import org.springframework.context.annotation.Profile;
45
import org.springframework.stereotype.Controller;
56
import org.springframework.web.bind.annotation.GetMapping;
@@ -10,9 +11,12 @@
1011
* <p>
1112
* Only matches paths without a file extension (e.g. /topology, /explorer/class)
1213
* so static assets (.js, .css, .html, .svg) are served normally.
14+
* <p>
15+
* Disabled when {@code codeiq.ui.enabled=false} (i.e. {@code --no-ui} flag passed to serve).
1316
*/
1417
@Controller
1518
@Profile("serving")
19+
@ConditionalOnProperty(name = "codeiq.ui.enabled", havingValue = "true", matchIfMissing = true)
1620
public class SpaController {
1721

1822
@GetMapping(value = {

src/main/resources/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ codeiq:
3030
max-depth: 10
3131
max-radius: 10
3232
batch-size: 500
33+
ui:
34+
enabled: true
3335

3436
spring.ai.mcp.server:
3537
name: code-iq

src/test/java/io/github/randomcodespace/iq/cli/ServeCommandTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,20 @@ void customPortIsParsed() {
4848
cmdLine.parseArgs("--port", "9090");
4949
assertEquals(9090, cmd.getPort());
5050
}
51+
52+
@Test
53+
void noUiDefaultsToFalse() {
54+
var cmd = new ServeCommand();
55+
var cmdLine = new CommandLine(cmd);
56+
cmdLine.parseArgs();
57+
assertEquals(false, cmd.isNoUi());
58+
}
59+
60+
@Test
61+
void noUiFlagIsRecognized() {
62+
var cmd = new ServeCommand();
63+
var cmdLine = new CommandLine(cmd);
64+
cmdLine.parseArgs("--no-ui");
65+
assertEquals(true, cmd.isNoUi());
66+
}
5167
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.github.randomcodespace.iq.web;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
5+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
6+
import org.springframework.context.annotation.Profile;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
/**
11+
* Verifies that SpaController is conditionally registered based on codeiq.ui.enabled.
12+
*/
13+
class SpaControllerConditionalTest {
14+
15+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
16+
.withUserConfiguration(SpaController.class)
17+
.withSystemProperties("spring.profiles.active=serving");
18+
19+
@Test
20+
void spaControllerHasConditionalOnPropertyAnnotation() {
21+
var annotation = SpaController.class.getAnnotation(ConditionalOnProperty.class);
22+
assertThat(annotation).isNotNull();
23+
assertThat(annotation.name()).contains("codeiq.ui.enabled");
24+
assertThat(annotation.havingValue()).isEqualTo("true");
25+
assertThat(annotation.matchIfMissing()).isTrue();
26+
}
27+
28+
@Test
29+
void spaControllerRegisteredWhenUiEnabledTrue() {
30+
contextRunner
31+
.withPropertyValues("codeiq.ui.enabled=true")
32+
.run(context -> assertThat(context).hasSingleBean(SpaController.class));
33+
}
34+
35+
@Test
36+
void spaControllerRegisteredWhenPropertyAbsent() {
37+
contextRunner
38+
.run(context -> assertThat(context).hasSingleBean(SpaController.class));
39+
}
40+
41+
@Test
42+
void spaControllerNotRegisteredWhenUiEnabledFalse() {
43+
contextRunner
44+
.withPropertyValues("codeiq.ui.enabled=false")
45+
.run(context -> assertThat(context).doesNotHaveBean(SpaController.class));
46+
}
47+
48+
@Test
49+
void spaControllerHasProfileAnnotation() {
50+
var annotation = SpaController.class.getAnnotation(Profile.class);
51+
assertThat(annotation).isNotNull();
52+
assertThat(annotation.value()).contains("serving");
53+
}
54+
}

0 commit comments

Comments
 (0)