Skip to content
Open
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
6 changes: 3 additions & 3 deletions apps/openadt-bootstrap/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"executor": "nx:run-commands",
"dependsOn": ["openadt-config:compile", "openadt-sap-adt:compile"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-bootstrap -am -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"cwd": "{workspaceRoot}"
},
"cache": true,
Expand All @@ -22,7 +22,7 @@
"executor": "nx:run-commands",
"dependsOn": ["compile"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -am -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs install -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: The second Maven invocation (install) is redundant because the 'compile' target already ensures the artifact is installed in the local repository. Removing it optimizes the build cycle.

Suggested change
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs install -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -f pom.xml",

"cwd": "{workspaceRoot}"
Comment on lines 24 to 26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using && to chain commands in a single command string is not fully cross-platform because Windows PowerShell (specifically Windows PowerShell 5.1, which is the default on Windows) does not support the && operator and will throw a syntax error. Since this PR aims to fix macOS/Windows cross-platform builds, we should use the commands array with "parallel": false in the nx:run-commands executor, which is fully cross-platform and safe. Additionally, the second command was missing the -q flag.

      "options": {
        "commands": [
          "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -f pom.xml",
          "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml"
        ],
        "parallel": false,
        "cwd": "{workspaceRoot}"

},
"cache": true,
Expand All @@ -37,7 +37,7 @@
"openadt-sap-adt:package"
],
"options": {
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-bootstrap -am -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-bootstrap -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"cwd": "{workspaceRoot}"
},
"cache": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,26 @@
List<SystemProfile> systems = new ArrayList<>();
parseClassicSystems(document, systems);
parseLoadBalancedSystems(document, systems);
detectExternalLandscapeUrls(document, path);
return systems;
} catch (ParserConfigurationException | SAXException e) {
throw new IOException("Failed to parse SAP GUI landscape: " + path, e);
}
}

private void detectExternalLandscapeUrls(Document document, Path sourcePath) {

Check warning on line 62 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused method parameter "sourcePath".

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdsIRHARCSOaV0pU&open=AZ6SgdsIRHARCSOaV0pU&pullRequest=39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Unused parameter sourcePath - the parameter is declared but never referenced in the method body.

NodeList includeNodes = document.getElementsByTagName("Include");
for (int i = 0; i < includeNodes.getLength(); i++) {
Element include = (Element) includeNodes.item(i);
String url = blankToNull(include.getAttribute("url"));
if (url != null) {
System.err.println("INFO: External SAP landscape URL detected: " + url);

Check warning on line 68 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.err by a logger.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdsIRHARCSOaV0pV&open=AZ6SgdsIRHARCSOaV0pV&pullRequest=39

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The code prints the full external landscape url value directly to stderr, which can leak embedded credentials or internal endpoints into CI logs and terminal history. Redact sensitive URL parts (user-info/query/fragment) or avoid printing the raw URL value. [security]

Severity Level: Critical 🚨
- ❌ External landscape URLs with credentials exposed in stderr logs.
- ⚠️ SetupAnalyzer CLI leaks internal SAP endpoints to operators.
Steps of Reproduction ✅
1. Create an SAP GUI landscape file containing an external include, for example `<Include
url="https://user:secret@example.com/SAPUILandscape.xml"/>`, and place it at a location
returned by `SetupPathLocator.sapGuiLandscapeFiles()` (e.g.
`~/Library/Preferences/SAP/SAPGUILandscape.xml` for macOS as configured in
SetupPathLocator.java:37-42).

2. Run the setup analysis flow, which constructs a default `SetupAnalyzer`
(SetupAnalyzer.java:25-36); its constructor wires in a `SapGuiLandscapeDetector` instance
at line 28 and later calls `analyze()` (SetupAnalyzer.java:45-51), causing each
`SystemDetector` to execute `detect()`.

3. Inside `SapGuiLandscapeDetector.detect()` (SapGuiLandscapeDetector.java:35-47), the
detector iterates over paths from `SetupPathLocator.sapGuiLandscapeFiles()`
(SapGuiLandscapeDetector.java:25-27) and, for each existing file, invokes
`parseLandscapeFile(path)` (SapGuiLandscapeDetector.java:49-56).

4. `parseLandscapeFile()` parses the XML into a `Document`
(SapGuiLandscapeDetector.java:51) and calls `detectExternalLandscapeUrls(document, path)`
(line 55); for each `<Include>` element with a non-blank `url` attribute
(SapGuiLandscapeDetector.java:62-67), `detectExternalLandscapeUrls()` executes
`System.err.println("INFO: External SAP landscape URL detected: " + url);` at line 68,
emitting the full external URL—including any embedded credentials, tokens, or internal
hostnames—directly to stderr and into CLI/CI logs.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java
**Line:** 68:68
**Comment:**
	*Security: The code prints the full external landscape `url` value directly to stderr, which can leak embedded credentials or internal endpoints into CI logs and terminal history. Redact sensitive URL parts (user-info/query/fragment) or avoid printing the raw URL value.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

System.err.println("INFO: Some system details (message server hostnames) may be in the external landscape.");

Check warning on line 69 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.err by a logger.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdsIRHARCSOaV0pW&open=AZ6SgdsIRHARCSOaV0pW&pullRequest=39
System.err.println("INFO: Open SAP GUI once to cache the full landscape, or provide message server manually.");

Check warning on line 70 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SapGuiLandscapeDetector.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this use of System.err by a logger.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdsIRHARCSOaV0pX&open=AZ6SgdsIRHARCSOaV0pX&pullRequest=39
}
}
}

private void parseClassicSystems(Document document, List<SystemProfile> systems) {
NodeList systemNodes = document.getElementsByTagName("System");
for (int i = 0; i < systemNodes.getLength(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,19 @@
paths.add(Path.of(home, "AppData", "Roaming", "SAP", SAP_COMMON, SAPUI_LANDSCAPE));
}
} else if (os.contains("mac")) {
paths.add(Path.of(home, "Library", "Application Support", "SAP", SAP_COMMON, SAPUI_LANDSCAPE));

Check failure on line 38 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Library" 3 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6cYnjjxuz03-S0Gi0u&open=AZ6cYnjjxuz03-S0Gi0u&pullRequest=39
// SAP GUI for Java stores landscape in Preferences with different filename

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Overall Code Complexity
This module has a mean cyclomatic complexity of 4.06 across 18 functions. The mean complexity threshold is 4

Suppress

paths.add(Path.of(home, "Library", "Preferences", "SAP", "SAPGUILandscape.xml"));
// SAP GUI for Java caches full landscape with message servers
Path cacheDir = Path.of(home, "Library", "Preferences", "SAP", "Cache", "SAPUILandscape");
if (Files.exists(cacheDir)) {
try (var stream = Files.list(cacheDir)) {
stream.filter(f -> f.toString().endsWith(".xml"))
.forEach(paths::add);
Comment on lines +45 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Cached landscape files are filtered with a case-sensitive .endsWith(".xml"), so valid files like .XML are silently skipped and never parsed. Use a case-insensitive extension check (as done elsewhere in this class) to avoid missing landscape entries. [logic error]

Severity Level: Major ⚠️
- ⚠️ Cached SAP GUI landscapes with .XML extensions ignored.
- ⚠️ Message server hostnames missing from detected system profiles.
Steps of Reproduction ✅
1. On macOS, ensure SAP GUI for Java has cached SAP landscape entries in
`~/Library/Preferences/SAP/Cache/SAPUILandscape` (the cache directory constructed in
`sapGuiLandscapeFiles()` at SetupPathLocator.java:41-43), and rename or create a cache
file with an uppercase extension such as `DEV.XML`.

2. Run `new SapGuiLandscapeDetector().detect()` so that its default constructor uses
`SetupPathLocator.sapGuiLandscapeFiles()` (SapGuiLandscapeDetector.java:24-27) to gather
both main landscape and cache files, and `detect()` (SapGuiLandscapeDetector.java:35-47)
iterates over those paths.

3. Inside `sapGuiLandscapeFiles()`, once `Files.exists(cacheDir)` returns true
(SetupPathLocator.java:43), the code lists the cache directory via `Files.list(cacheDir)`
and applies `stream.filter(f -> f.toString().endsWith(".xml"))` at line 45 before adding
entries to the `paths` set at line 46.

4. Because `.endsWith(".xml")` is case-sensitive and operates on the raw path string, a
cache file named `DEV.XML` does not match the filter and is excluded, unlike other XML
detection paths in this class that normalize to lowercase (see `listXmlFiles()` at
SetupPathLocator.java:31-39); as a result, `SapGuiLandscapeDetector.parseLandscapeFile()`
(SapGuiLandscapeDetector.java:49-56) never parses that cached landscape, and any message
server details present only in the cache remain absent from detected `SystemProfile`
entries.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java
**Line:** 45:46
**Comment:**
	*Logic Error: Cached landscape files are filtered with a case-sensitive `.endsWith(".xml")`, so valid files like `.XML` are silently skipped and never parsed. Use a case-insensitive extension check (as done elsewhere in this class) to avoid missing landscape entries.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

} catch (IOException e) {
// Ignore cache read errors
}
}
Comment on lines +39 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Complex Method
sapGuiLandscapeFiles has a cyclomatic complexity of 9, threshold = 9

Suppress

Comment on lines +39 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Bumpy Road Ahead
sapGuiLandscapeFiles has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

Suppress

}

for (Path windowsHome : windowsUserHomes()) {
Expand Down Expand Up @@ -103,6 +115,23 @@

static List<Path> jcoJarRoots() {
List<Path> paths = new ArrayList<>();
String os = System.getProperty(OS_NAME_PROPERTY, "").toLowerCase(Locale.ROOT);
String home = System.getProperty(USER_HOME_PROPERTY, "");

// macOS and Linux: check user's home directory for Eclipse p2 pool
if (!home.isBlank() && (os.contains("mac") || os.contains("nix") || os.contains("nux"))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Complex Conditional
jcoJarRoots has 1 complex conditionals with 3 branches, threshold = 2

Suppress

paths.add(Path.of(home, ".p2", "pool", "plugins"));

Check failure on line 123 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "plugins" 4 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pP&open=AZ6SgdoHRHARCSOaV0pP&pullRequest=39
}

// macOS: Eclipse.app bundle installations
if (os.contains("mac")) {
paths.add(Path.of("/Applications", "Eclipse.app", "Contents", "Eclipse", "plugins"));

Check failure on line 128 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Eclipse" 5 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pO&open=AZ6SgdoHRHARCSOaV0pO&pullRequest=39

Check failure on line 128 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Contents" 5 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pQ&open=AZ6SgdoHRHARCSOaV0pQ&pullRequest=39

Check failure on line 128 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Eclipse.app" 4 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pR&open=AZ6SgdoHRHARCSOaV0pR&pullRequest=39

Check failure on line 128 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "/Applications" 3 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pS&open=AZ6SgdoHRHARCSOaV0pS&pullRequest=39
paths.add(Path.of("/Applications", "SAP Business Application Studio.app", "Contents", "Eclipse", "plugins"));
if (!home.isBlank()) {
paths.add(Path.of(home, "Applications", "Eclipse.app", "Contents", "Eclipse", "plugins"));
}
}
Comment on lines +123 to +133

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Deduplicate repeated path literals to satisfy static analysis and reduce path drift risk.

The new macOS path additions repeat literals ("Eclipse.app", "Contents", "Eclipse", "plugins", "libsapcrypto.dylib") that are already flagged by Sonar failures. Promote these to constants and reuse them across the new path builders.

Also applies to: 145-147, 185-188

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[failure] 118-118: Define a constant instead of duplicating this literal "Eclipse" 5 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pO&open=AZ6SgdoHRHARCSOaV0pO&pullRequest=39


[failure] 118-118: Define a constant instead of duplicating this literal "/Applications" 3 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pS&open=AZ6SgdoHRHARCSOaV0pS&pullRequest=39


[failure] 118-118: Define a constant instead of duplicating this literal "Contents" 5 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pQ&open=AZ6SgdoHRHARCSOaV0pQ&pullRequest=39


[failure] 118-118: Define a constant instead of duplicating this literal "Eclipse.app" 4 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pR&open=AZ6SgdoHRHARCSOaV0pR&pullRequest=39


[failure] 113-113: Define a constant instead of duplicating this literal "plugins" 4 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pP&open=AZ6SgdoHRHARCSOaV0pP&pullRequest=39

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java`
around lines 113 - 123, The repeated macOS path literal fragments in
SetupPathLocator (e.g., the Path.of(...) calls that use "Eclipse.app",
"Contents", "Eclipse", "plugins", and "libsapcrypto.dylib") should be extracted
into private static final String constants (for example ECLIPSE_APP,
CONTENTS_DIR, ECLIPSE_DIR, PLUGINS_DIR, LIB_SAP_CRYPTO) and then reused when
constructing paths in the methods that call paths.add(Path.of(...)) (including
the blocks shown and the similar code at the other flagged locations around the
other Path.of calls). Replace the repeated string literals with the new
constants to remove duplication and ensure consistent reuse across
SetupPathLocator.


for (Path windowsHome : windowsUserHomes()) {
paths.add(windowsHome.resolve(".p2/pool/plugins"));
}
Expand All @@ -113,6 +142,20 @@

static List<Path> jcoNativeSearchRoots() {
List<Path> paths = new ArrayList<>();
String os = System.getProperty(OS_NAME_PROPERTY, "").toLowerCase(Locale.ROOT);
String home = System.getProperty(USER_HOME_PROPERTY, "");

// macOS-specific paths for JCo native libraries
if (os.contains("mac") && !home.isBlank()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Absolute system paths (/usr/local/lib, /Library/Java/Extensions, /Applications/Eclipse.app/...) are gated behind !home.isBlank(), but they don't depend on the user's home directory. If user.home is blank (e.g., in constrained runtimes), these system-wide paths won't be searched. Split the condition so home-dependent paths (like home/lib, home/.p2) are guarded by the blank check, while absolute paths are only guarded by the OS check.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java, line 139:

<comment>Absolute system paths (`/usr/local/lib`, `/Library/Java/Extensions`, `/Applications/Eclipse.app/...`) are gated behind `!home.isBlank()`, but they don't depend on the user's home directory. If `user.home` is blank (e.g., in constrained runtimes), these system-wide paths won't be searched. Split the condition so home-dependent paths (like `home/lib`, `home/.p2`) are guarded by the blank check, while absolute paths are only guarded by the OS check.</comment>

<file context>
@@ -113,6 +132,20 @@ static List<Path> jcoJarRoots() {
+        String home = System.getProperty(USER_HOME_PROPERTY, "");
+
+        // macOS-specific paths for JCo native libraries
+        if (os.contains("mac") && !home.isBlank()) {
+            paths.add(Path.of(home, "lib"));
+            paths.add(Path.of(home, ".p2"));
</file context>

paths.add(Path.of(home, "lib"));
paths.add(Path.of(home, ".p2"));
paths.add(Path.of("/usr", "local", "lib"));
paths.add(Path.of("/Library", "Java", "Extensions"));
// Eclipse.app OSGi bundle cache (where JCo native lib is extracted)
paths.add(Path.of("/Applications", "Eclipse.app", "Contents", "Eclipse", "configuration", "org.eclipse.osgi"));
paths.add(Path.of(home, "Applications", "Eclipse.app", "Contents", "Eclipse", "configuration", "org.eclipse.osgi"));
}
Comment on lines +149 to +157

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don’t gate absolute macOS search paths on user.home.

Both macOS blocks skip /usr/local/lib and /Library/... when user.home is blank, even though those paths are independent of home. Split home-dependent and absolute candidates so discovery remains robust in constrained runtimes.

Also applies to: 184-188

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java`
around lines 139 - 147, The macOS path-building in SetupPathLocator currently
wraps both absolute and home-dependent candidates behind a check for non-blank
user.home; update the logic in the method that builds mac paths (the block that
checks os.contains("mac")) to always add the absolute, system-level paths (e.g.,
Path.of("/usr","local","lib") and Path.of("/Library","Java","Extensions"), and
the Eclipse.app OSGi cache at Path.of("/Applications","Eclipse.app",...))
regardless of home.isBlank(), while only adding the user-specific candidates
(e.g., Path.of(home,"lib"), Path.of(home,".p2"),
Path.of(home,"Applications","Eclipse.app",...)) when home is non-blank; apply
the same separation to the analogous macOS block later in the class (the second
occurrence around the other mac block).


for (Path windowsHome : windowsUserHomes()) {
paths.add(windowsHome.resolve("Documents"));
paths.add(windowsHome.resolve("AppData/Local"));
Expand Down Expand Up @@ -144,6 +187,16 @@

static List<Path> sapcryptoCandidates() {
List<Path> paths = new ArrayList<>();
String os = System.getProperty(OS_NAME_PROPERTY, "").toLowerCase(Locale.ROOT);
String home = System.getProperty(USER_HOME_PROPERTY, "");

// macOS-specific paths for SAP Crypto
if (os.contains("mac") && !home.isBlank()) {
paths.add(Path.of(home, "lib", "libsapcrypto.dylib"));

Check failure on line 195 in apps/openadt-bootstrap/src/main/java/org/openadt/bootstrap/SetupPathLocator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "libsapcrypto.dylib" 3 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6SgdoHRHARCSOaV0pT&open=AZ6SgdoHRHARCSOaV0pT&pullRequest=39
paths.add(Path.of("/usr", "local", "lib", "libsapcrypto.dylib"));
paths.add(Path.of("/Library", "Frameworks", "sapcrypto.framework", "Versions", "Current", "libsapcrypto.dylib"));
}

for (Path programFilesRoot : windowsProgramFilesRoots()) {
paths.add(programFilesRoot.resolve("SAP/FrontEnd/SecureLogin/lib/sapcrypto.dll"));
}
Expand Down
6 changes: 3 additions & 3 deletions apps/openadt-cli/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"openadt-bootstrap:compile"
],
"options": {
"command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -f pom.xml && bun run scripts/dev-runtime-classpath.ts",
"command": "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml && bun run scripts/dev-runtime-classpath.ts",
"cwd": "{workspaceRoot}"
Comment on lines 17 to 19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using && to chain commands in a single command string is not fully cross-platform because Windows PowerShell 5.1 (the default on Windows) does not support the && operator. To ensure reliable cross-platform execution on Windows, use the commands array with "parallel": false instead.

      "options": {
        "commands": [
          "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
          "bun run scripts/dev-runtime-classpath.ts"
        ],
        "parallel": false,
        "cwd": "{workspaceRoot}"

},
"cache": true,
Expand All @@ -26,7 +26,7 @@
"executor": "nx:run-commands",
"dependsOn": ["package"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -f pom.xml",
Comment on lines 27 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if any Maven POMs declare openadt-cli as a dependency

rg -t xml --iglob '**/pom.xml' -C3 '<artifactId>openadt-cli</artifactId>'

Repository: abapify/openadt

Length of output: 41


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Read the relevant parts of openadt-cli/project.json
echo "---- apps/openadt-cli/project.json (lines 1-120) ----"
nl -ba apps/openadt-cli/project.json | sed -n '1,120p'

# 2) Find comparable project.json files for bootstrap and sap-adt
echo "---- Find project.json under apps/openadt-* ----"
find apps -maxdepth 3 -name project.json | sed -n '1,200p'

# 3) Read relevant sections from bootstrap/sap-adt project.json if present
for f in $(find apps -maxdepth 4 -name project.json | sort); do
  if echo "$f" | rg -q 'openadt-(bootstrap|sap-adt)'; then
    echo "---- $f (lines 1-160) ----"
    nl -ba "$f" | sed -n '1,160p'
  fi
done

# 4) Double-check if openadt-cli is referenced in any non-pom config (quick search)
echo "---- Search for openadt-cli references across repo ----"
rg -n --hidden --no-ignore -S "openadt-cli" -g'*.{xml,yml,yaml,json,properties,gradle,kts,sh,mjs,js,pom}' | head -n 200

Repository: abapify/openadt

Length of output: 174


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- apps/openadt-cli/project.json (lines 1-160) ----"
cat -n apps/openadt-cli/project.json | sed -n '1,160p'

echo "---- Find project.json under apps/openadt-* ----"
find apps -maxdepth 3 -name project.json | sort

for f in $(find apps -maxdepth 4 -name project.json | sort); do
  if echo "$f" | rg -q 'openadt-(bootstrap|sap-adt)'; then
    echo "---- $f (lines 1-220) ----"
    cat -n "$f" | sed -n '1,220p'
  fi
done

echo "---- Search for openadt-cli references across repo (limited) ----"
rg -n --hidden --no-ignore -S "openadt-cli" -g'*.{xml,yml,yaml,json,properties,gradle,kts,sh,mjs,js,pom}' | head -n 200

Repository: abapify/openadt

Length of output: 9113


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- apps/openadt-cli/pom.xml: artifactId/groupId ----"
rg -n --no-heading "<(groupId|artifactId)>" apps/openadt-cli/pom.xml | head -n 80

artifactId="$(rg -n --no-heading "<artifactId>" apps/openadt-cli/pom.xml | head -n 1 | sed -E 's/.*<artifactId>([^<]+)<\/artifactId>.*/\1/' || true)"
echo "Detected artifactId: ${artifactId:-<none>}"

echo "---- Other pom.xml files depending on that artifactId (excluding openadt-cli/pom.xml) ----"
if [ -n "${artifactId}" ]; then
  rg -t xml --iglob '**/pom.xml' --glob '!apps/openadt-cli/pom.xml' -n --no-heading "<artifactId>${artifactId}</artifactId>" -C2 || true
fi

echo "---- Check whether any pom references openadt-cli module directory directly ----"
rg -n --no-heading "<module>apps/openadt-cli</module>" -S pom.xml **/pom.xml || true

Repository: abapify/openadt

Length of output: 3750


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- apps/openadt-cli/pom.xml (top + dependencies section excerpt) ----"
cat -n apps/openadt-cli/pom.xml | sed -n '1,220p'

echo "---- apps/openadt-cli/pom.xml: dependency coordinates (groupId/artifactId pairs) ----"
rg -n --no-heading "<dependency>" -n apps/openadt-cli/pom.xml >/dev/null || true
python3 - <<'PY'
import re, pathlib
p=pathlib.Path("apps/openadt-cli/pom.xml").read_text()
# crude parse: capture groupId/artifactId inside <dependency> blocks
deps=[]
for m in re.finditer(r"<dependency>\s*(.*?)\s*</dependency>", p, flags=re.S):
    block=m.group(1)
    gid=re.search(r"<groupId>\s*([^<]+)\s*</groupId>", block)
    aid=re.search(r"<artifactId>\s*([^<]+)\s*</artifactId>", block)
    scope=re.search(r"<scope>\s*([^<]+)\s*</scope>", block)
    if aid:
        deps.append((gid.group(1).strip() if gid else None, aid.group(1).strip(), scope.group(1).strip() if scope else None))
print("\n".join([f"{g or ''}:{a} (scope={s or 'none'})" for g,a,s in deps]))
PY

echo "---- Search other module poms for dependency on org.openadt:openadt ----"
rg -t xml --iglob '**/pom.xml' --glob '!apps/openadt-cli/pom.xml' -n "<groupId>org.openadt</groupId>|<artifactId>openadt</artifactId>" -C1

echo "---- Search other module poms for artifactId openadt (quick) ----"
rg -t xml --iglob '**/pom.xml' --glob '!apps/openadt-cli/pom.xml' -n --no-heading "<artifactId>openadt</artifactId>" -C2 || true

echo "---- apps/openadt-cli/pom.xml: test-related plugins ----"
rg -n --no-heading "(maven-surefire-plugin|maven-failsafe-plugin|surefire|failsafe|jacoco|skipTests|testFailure|maven-surefire)" apps/openadt-cli/pom.xml -S || true

echo "---- apps/openadt-cli/pom.xml: shade/assembly configuration ----"
rg -n --no-heading "(maven-shade-plugin|shade|assembly|classifier|repackage|manifest|mainClass|distribution)" apps/openadt-cli/pom.xml -S || true

Repository: abapify/openadt

Length of output: 14446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Find dependencies on org.openadt:openadt in module poms ----"
rg -t xml --iglob 'apps/*/pom.xml' --glob '!apps/openadt-cli/pom.xml' -n \
  "<groupId>org\.openadt</groupId>[\s\S]*?<artifactId>openadt</artifactId>" \
  -C2 || true

echo "---- Find any pom dependencies with <artifactId>openadt</artifactId> (show groupId around) ----"
rg -t xml --iglob 'apps/*/pom.xml' -n "<artifactId>openadt</artifactId>" -C3 || true

echo "---- Check if any pom depends on the openadt-cli module artifactId openadt ----"
rg -t xml --iglob '**/pom.xml' --glob '!**/target/**' -n "<artifactId>openadt</artifactId>" -C1 || true

echo "---- Search for openadt-cli as a dependency (module name) ----"
rg -t xml --iglob '**/pom.xml' -n "openadt-cli" -C2 || true

Repository: abapify/openadt

Length of output: 1084


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- openadt-cli test sources (files) ----"
find apps/openadt-cli/src/test -type f | head -n 200

echo "---- Search for references to shaded/distribution jar or target paths in openadt-cli tests ----"
rg -n --no-heading "target/|shade|shaded|distribution|openadt.*\\.jar|\\.jar\\b|maven-shade|OpenAdtCommand" apps/openadt-cli/src/test || true

echo "---- openadt-cli surefire/failsafe config in pom.xml ----"
rg -n --no-heading "(maven-surefire-plugin|maven-failsafe-plugin|surefire|failsafe|skipTests|skipITs)" apps/openadt-cli/pom.xml || true

echo "---- Check whether any other module depends on the openadt artifactId openadt (global scan) ----"
rg -t xml --iglob 'apps/*/pom.xml' --glob '!apps/openadt-cli/pom.xml' -n "<artifactId>openadt</artifactId>" -C2 || true

echo "---- Check whether any module depends on org.openadt groupId and artifactId=openadt (global scan) ----"
rg -t xml --iglob 'apps/*/pom.xml' --glob '!apps/openadt-cli/pom.xml' -n "<groupId>org.openadt</groupId>" -C1 | head -n 200

Repository: abapify/openadt

Length of output: 4147


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and show mvnw-runner.mjs behavior (args handling)
ls -la scripts/mvnw-runner.mjs || true
sed -n '1,220p' scripts/mvnw-runner.mjs || true

# Also check if any nx target overrides args or wraps -am internally
rg -n "mvnw-runner\.mjs" -S . | head -n 50

# Confirm whether openadt-cli:package has dependsOn and whether openadt-cli:test needs deps
cat -n apps/openadt-cli/project.json | sed -n '1,120p'

Repository: abapify/openadt

Length of output: 6399


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- package.json scripts ----"
cat -n package.json | sed -n '1,120p'

echo "---- Search CI/workflows for nx test openadt-cli ----"
rg -n "nx\s+test\s+openadt-cli|openadt:test" .github package.json . | head -n 200

Repository: abapify/openadt

Length of output: 2270


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Search GitHub workflows for openadt:test or nx test openadt-cli ----"
rg -n "openadt:test|nx\s+test\s+openadt-cli|nx\s+run\s+openadt-cli:test" .github/workflows -S || true

echo "---- Search workflows for openadt-cli compile/package/install targets ----"
rg -n "nx\s+(run\s+)?openadt-cli:(compile|package|test|ensure-dev-jar)|nx\s+(compile|package|test)\s+openadt-cli" .github/workflows -S || true

echo "---- Search workflows for any prior installs of openadt-config/openadt-bootstrap/openadt-sap-adt ----"
rg -n "openadt-(config|bootstrap|sap-adt):" .github/workflows -S || true

echo "---- List workflows ----"
ls -la .github/workflows

Repository: abapify/openadt

Length of output: 564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- .github/workflows/ci.yml ----"
cat -n .github/workflows/ci.yml | sed -n '1,260p'

echo "---- .github/workflows/release.yml ----"
cat -n .github/workflows/release.yml | sed -n '1,260p'

Repository: abapify/openadt

Length of output: 11644


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- scripts/ci-java-test.sh ----"
cat -n scripts/ci-java-test.sh | sed -n '1,220p'

Repository: abapify/openadt

Length of output: 883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Scan all pom.xml for artifactId openadt (org.openadt module) ----"
rg -t xml --iglob '**/pom.xml' -n "<artifactId>openadt</artifactId>" -C1 || true

echo "---- Scan all pom.xml for groupId org.openadt + artifactId openadt together ----"
rg -t xml --iglob '**/pom.xml' -n "<groupId>org\.openadt</groupId>" -C1 | head -n 200

Repository: abapify/openadt

Length of output: 3016


Fix apps/openadt-cli/project.json Nx test target dependency chain

targets.test currently depends on ["package"] and runs mvnw-runner.mjs ... -q test -pl apps/openadt-cli ... without first compiling/install-ing openadt-config, openadt-sap-adt, or openadt-bootstrap (those are only ensured by the compile target). In contrast, openadt-bootstrap and openadt-sap-adt test targets depend on compile (and also append an install -DskipTests step).

The missing post-test install for the openadt-cli artifact itself isn’t the main problem (no other module depends on org.openadt:openadt), but the current test chain can leave required Maven dependencies unavailable when nx test openadt-cli is run in isolation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/openadt-cli/project.json` around lines 27 - 29, The test target
(targets.test) for apps/openadt-cli currently depends only on "package" and runs
the mvnw-runner.mjs test command without ensuring the project graph is
compiled/installed; update targets.test to depend on "compile" (in addition to
or instead of "package") so openadt-config/openadt-sap-adt/openadt-bootstrap are
built first, and modify the mvnw-runner.mjs invocation used by targets.test to
append an install step (install -DskipTests) after the test phase (matching the
pattern used by openadt-bootstrap and openadt-sap-adt) so required Maven
artifacts are available when running nx test openadt-cli in isolation.

"cwd": "{workspaceRoot}"
},
"cache": true,
Expand All @@ -36,7 +36,7 @@
"package": {
"executor": "nx:run-commands",
"options": {
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: package target dropped Maven -am without adding Nx dependsOn for upstream modules, breaking isolated builds

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/openadt-cli/project.json, line 39:

<comment>`package` target dropped Maven `-am` without adding Nx `dependsOn` for upstream modules, breaking isolated builds</comment>

<file context>
@@ -36,7 +36,7 @@
       "executor": "nx:run-commands",
       "options": {
-        "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-cli -am -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
+        "command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-cli -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
         "cwd": "{workspaceRoot}"
       },
</file context>

"cwd": "{workspaceRoot}"
},
"cache": true,
Expand Down
6 changes: 3 additions & 3 deletions apps/openadt-config/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"compile": {
"executor": "nx:run-commands",
"options": {
"command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-config -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q install -N -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml && node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"cwd": "{workspaceRoot}"
Comment on lines 12 to 14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using && to chain commands in a single command string is not fully cross-platform because Windows PowerShell 5.1 (the default on Windows) does not support the && operator. To ensure reliable cross-platform execution on Windows, use the commands array with "parallel": false instead.

      "options": {
        "commands": [
          "node scripts/mvnw-runner.mjs -q install -N -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
          "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml"
        ],
        "parallel": false,
        "cwd": "{workspaceRoot}"

},
"cache": true,
Expand All @@ -21,7 +21,7 @@
"executor": "nx:run-commands",
"dependsOn": ["compile"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: This second Maven invocation is redundant. The 'compile' target already populates the local repository with the required artifacts. Removing this step reduces build overhead.

Suggested change
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -f pom.xml",

"cwd": "{workspaceRoot}"
Comment on lines 23 to 25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using && to chain commands in a single command string is not fully cross-platform because Windows PowerShell 5.1 (the default on Windows) does not support the && operator. To ensure reliable cross-platform execution on Windows, use the commands array with "parallel": false instead.

      "options": {
        "commands": [
          "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -f pom.xml",
          "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml"
        ],
        "parallel": false,
        "cwd": "{workspaceRoot}"

},
"cache": true,
Expand All @@ -32,7 +32,7 @@
"executor": "nx:run-commands",
"dependsOn": ["compile"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-config -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-config -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"cwd": "{workspaceRoot}"
},
"cache": true,
Expand Down
6 changes: 3 additions & 3 deletions apps/openadt-sap-adt/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"executor": "nx:run-commands",
"dependsOn": ["openadt-config:compile"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q compile -pl apps/openadt-sap-adt -am -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"cwd": "{workspaceRoot}"
},
"cache": true,
Expand All @@ -22,7 +22,7 @@
"executor": "nx:run-commands",
"dependsOn": ["compile"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -am -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs install -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Suggestion: The second Maven invocation is redundant. The artifact is already installed in the local repository during the 'compile' phase. Removing this call speeds up the test execution.

Suggested change
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -f pom.xml && node scripts/mvnw-runner.mjs install -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -f pom.xml",

"cwd": "{workspaceRoot}"
Comment on lines 24 to 26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using && to chain commands in a single command string is not fully cross-platform because Windows PowerShell 5.1 (the default on Windows) does not support the && operator. To ensure reliable cross-platform execution on Windows, use the commands array with "parallel": false instead. Additionally, the second command was missing the -q flag.

      "options": {
        "commands": [
          "node scripts/mvnw-runner.mjs -q test -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -f pom.xml",
          "node scripts/mvnw-runner.mjs -q install -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml"
        ],
        "parallel": false,
        "cwd": "{workspaceRoot}"

},
"cache": true,
Expand All @@ -33,7 +33,7 @@
"executor": "nx:run-commands",
"dependsOn": ["compile", "openadt-config:package"],
"options": {
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-sap-adt -am -DskipTests -f pom.xml",
"command": "node scripts/mvnw-runner.mjs -q package -pl apps/openadt-sap-adt -Pdistribution -Dopenadt.distribution=true -DskipTests -f pom.xml",
"cwd": "{workspaceRoot}"
},
"cache": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@

public String webAdapterStatus(String profileId) throws IOException, InterruptedException {
String encodedProfileId = URLEncoder.encode(profileId, StandardCharsets.UTF_8);
// Try API v3 first (Windows), fall back to v2 (macOS)
try {
return webAdapterStatusV3(encodedProfileId);
} catch (IOException e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Fallback from v3 to v2 catches all IOExceptions, masking genuine v3 errors (auth failures, server errors) by making a second request to v2

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java, line 94:

<comment>Fallback from v3 to v2 catches all IOExceptions, masking genuine v3 errors (auth failures, server errors) by making a second request to v2</comment>

<file context>
@@ -88,6 +88,15 @@ public boolean isReachable() {
+        // Try API v3 first (Windows), fall back to v2 (macOS)
+        try {
+            return webAdapterStatusV3(encodedProfileId);
+        } catch (IOException e) {
+            return webAdapterStatusV2(encodedProfileId);
+        }
</file context>

return webAdapterStatusV2(encodedProfileId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Preserve the original v3 IOException when falling back to v2. If v2 also fails, this currently drops the initial v3 failure context, making root-cause diagnosis harder.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java, line 95:

<comment>Preserve the original v3 IOException when falling back to v2. If v2 also fails, this currently drops the initial v3 failure context, making root-cause diagnosis harder.</comment>

<file context>
@@ -88,6 +88,15 @@ public boolean isReachable() {
+        try {
+            return webAdapterStatusV3(encodedProfileId);
+        } catch (IOException e) {
+            return webAdapterStatusV2(encodedProfileId);
+        }
+    }
</file context>
Suggested change
return webAdapterStatusV2(encodedProfileId);
try {
return webAdapterStatusV2(encodedProfileId);
} catch (IOException v2Error) {
v2Error.addSuppressed(e);
throw v2Error;
}

}
}
Comment on lines 89 to +97

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve v3 error context when falling back to v2.

When v3 fails with IOException and the fallback to v2 also fails, only the v2 error is propagated—the original v3 failure is lost. This makes debugging harder because users won't know that v3 was attempted first or why it failed.

Additionally, the broad IOException catch means any failure (network timeout, parse error, HTTP 404) triggers the v2 fallback. If v3 fails due to a network issue, v2 will likely fail the same way, and the dual attempt adds latency without helping the user understand the root cause.

🔍 Suggested improvement: Add suppressed exception and logging
 public String webAdapterStatus(String profileId) throws IOException, InterruptedException {
     String encodedProfileId = URLEncoder.encode(profileId, StandardCharsets.UTF_8);
     // Try API v3 first (Windows), fall back to v2 (macOS)
     try {
         return webAdapterStatusV3(encodedProfileId);
     } catch (IOException e) {
+        CliLog.info("Secure Login hub API v3 status failed (" + e.getMessage() + "), trying v2 fallback");
+        try {
             return webAdapterStatusV2(encodedProfileId);
+        } catch (IOException v2Error) {
+            v2Error.addSuppressed(e);
+            throw v2Error;
+        }
     }
 }

This preserves the v3 error as a suppressed exception and logs the fallback attempt. Apply the same pattern to loginWebAdapter at lines 131-138.

Note: The same issue and fix apply to the loginWebAdapter fallback at lines 131-138.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java`
around lines 89 - 97, In webAdapterStatus(String profileId) and similarly in
loginWebAdapter, preserve the original v3 IOException when falling back to v2 by
catching the v3 exception, logging an explicit message that v3 failed and v2 is
being attempted (include the v3 exception message), then call the v2 method and
if v2 also throws, add the saved v3 IOException as a suppressed exception on the
v2 exception before rethrowing; also consider narrowing the catch to only the
specific exceptions that should trigger a fallback (rather than broad
IOException) so transient/network errors can be handled appropriately.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for tests covering the v2 fallback behavior

# Search for test methods that might cover v2 fallback
rg -n -C5 'slc2/api/(status|login)' --type=java --glob='*Test.java'

# Search for test assertions that check fallback behavior
rg -n -C3 '(webAdapterStatus|loginWebAdapter).*fallback|v2.*fallback|IOException.*v2' --type=java --glob='*Test.java'

Repository: abapify/openadt

Length of output: 41


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the main test class for SecureLoginHubClient
fd -i 'SecureLoginHubClientTest.java' -a . || true
fd -i 'SecureLoginHubClient*Test.java' -a . || true

# Find direct references/usages of v2 fallback helpers in tests
rg -n --glob='*Test.java' 'webAdapterStatusV2|loginWebAdapterV2|webAdapterStatusV3|loginWebAdapterV3' . || true

# Search for any stubbing/mocking of slc2/api endpoints for v2 specifically
rg -n --glob='*Test.java' 'slc2/api' . || true
rg -n --glob='*Test.java' 'slc2/api/.*(v2|_v2|version.?2)' . || true

# Search for fallback-related assertions/flow in tests
rg -n --glob='*Test.java' '(fallback|v3 fails|v2 succeeds|catch \(IOException|IOException.*v2)' . || true

# Narrow search to the secure login hub client test files (if present)
TESTS="$(fd -i 'SecureLoginHubClient*Test.java' . -a || true)"
if [ -n "$TESTS" ]; then
  rg -n --glob='*Test.java' 'webAdapterStatus|loginWebAdapter' $TESTS || true
fi

Repository: abapify/openadt

Length of output: 2652


🏁 Script executed:

#!/bin/bash
set -euo pipefail

TEST_FILE="apps/openadt-sap-adt/src/test/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClientTest.java"

# Show key parts around the existing assertions
nl -ba "$TEST_FILE" | sed -n '1,220p'

# Targeted searches inside the test file for v2/v3/fallback hints
echo "---- rg v2/v3/fallback in SecureLoginHubClientTest.java ----"
rg -n --glob='*.java' '(webAdapterStatusV[23]|loginWebAdapterV[23]|fallback|v3|v2|slc2/api|/v2|/v3|statusV[23])' "$TEST_FILE" || true

echo "---- Search within the test file for IOException-driven fallback patterns ----"
rg -n --glob='*.java' '(catch\s*\(\s*IOException|throws\s*IOException|IOException)' "$TEST_FILE" || true

# Also find any helper test client setup that might indicate endpoint versions
rg -n --glob='*.java' '(mock|stub|wireMock|server|endpoint|url|baseUrl|host|scheme)' "$TEST_FILE" || true

Repository: abapify/openadt

Length of output: 102


🏁 Script executed:

#!/bin/bash
set -euo pipefail

TEST_FILE="apps/openadt-sap-adt/src/test/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClientTest.java"

# Show key parts around the existing assertions using cat -n
sed -n '1,220p' "$TEST_FILE" | cat -n

echo "---- rg v2/v3/fallback hints in SecureLoginHubClientTest.java ----"
rg -n --glob='*.java' '(webAdapterStatusV[23]|loginWebAdapterV[23]|fallback|v3|v2|slc2/api|/v2|/v3|statusV[23])' "$TEST_FILE" || true

echo "---- Search within the test file for IOException-driven fallback patterns ----"
rg -n --glob='*.java' '(catch\s*\(\s*IOException|throws\s*IOException|IOException)' "$TEST_FILE" || true

echo "---- Search within the test file for mocking/endpoint/version hints ----"
rg -n --glob='*.java' '(mock|stub|wireMock|server|endpoint|url|baseUrl|host|scheme)' "$TEST_FILE" || true

Repository: abapify/openadt

Length of output: 9940


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search all test sources for v2 (slc2) endpoint usage and fallback mentions
rg -n --glob='*Test.java' 'slc2/api/(status|login)' . || true
rg -n --glob='*Test.java' '(webAdapterStatusV2|loginWebAdapterV2|webAdapterStatusV3|loginWebAdapterV3)' . || true
rg -n --glob='*Test.java' '(webAdapterStatus|loginWebAdapter).*catch\s*\(IOException|fallback|v2|slc2)' . || true

Repository: abapify/openadt

Length of output: 260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search all test sources for v2 (slc2) endpoint usage
rg -n --glob='*Test.java' 'slc2/api/(status|login)' . || true

# Search all test sources for explicit v2/v3 helper method names
rg -n --glob='*Test.java' '(webAdapterStatusV[23]|loginWebAdapterV[23])' . || true

# Search for fallback wording and IOException patterns without problematic regex
rg -n --glob='*Test.java' 'fallback' . || true
rg -n --glob='*Test.java' 'IOException' . || true
rg -n --glob='*Test.java' 'v2' . || true
rg -n --glob='*Test.java' 'v3' . || true

Repository: abapify/openadt

Length of output: 10077


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for v2 endpoint paths (secure login hub v2)
rg -n --glob='*Test.java' '/slc2/api/(status|login)' . || true

# Search for v3 endpoint paths (already seen /slc3/api/status in SecureLoginHubClientTest)
rg -n --glob='*Test.java' '/slc3/api/(status|login)' . || true

# Search for direct references to the v2 helper method names
rg -n --glob='*Test.java' 'webAdapterStatusV2|loginWebAdapterV2' . || true

# Search for try/catch patterns mentioning v3->v2 fallback in tests
rg -n --glob='*Test.java' 'webAdapterStatusV3.*catch|loginWebAdapterV3.*catch|fallback to v2|fall back to v2|v2 \\(macOS\\)' . || true

Repository: abapify/openadt

Length of output: 211


Add test coverage for SecureLoginHubClient’s v3→v2 fallback paths

SecureLoginHubClientTest only validates the v3 status endpoint (assertEquals("/slc3/api/status", lastPath)) and doesn’t exercise any /slc2/api/... behavior or the “v3 throws → v2 succeeds/fails” fallback scenarios. Extend tests to cover:

  • v3 failing with different IOException causes (e.g., 404, timeout, response/parse issues) and v2 succeeding afterward
  • both v3 and v2 failing (assert the resulting exception/behavior)

Also applies to: 131-138

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java`
around lines 89 - 97, Tests only cover the v3 path for webAdapterStatus; add
unit tests in SecureLoginHubClientTest to exercise the v3→v2 fallback and
both-fail cases by stubbing/mocking webAdapterStatusV3 and webAdapterStatusV2
behavior: create tests where webAdapterStatusV3(encodedProfileId) throws
different IOExceptions (e.g., simulate 404, timeout, parse error) and assert
that webAdapterStatus then calls webAdapterStatusV2(encodedProfileId) and
returns its success result, and add tests where both webAdapterStatusV3 and
webAdapterStatusV2 throw to assert the propagated exception/behavior; reference
the methods webAdapterStatus, webAdapterStatusV3, webAdapterStatusV2 to locate
and mock the behavior in the test.


private String webAdapterStatusV3(String encodedProfileId) throws IOException, InterruptedException {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Code Duplication
The module contains 4 functions with similar structure: loginWebAdapterV2,loginWebAdapterV3,webAdapterStatusV2,webAdapterStatusV3

Suppress

URI uri = URI.create(hubBaseUrl + "/slc3/api/status?profileid=" + encodedProfileId);
HttpResponse<String> response = httpClient.send(
hubRequest(uri).GET().build(),
Expand All @@ -98,14 +107,37 @@
}
JsonNode root = objectMapper.readTree(response.body());
JsonNode status = root.path("status");
return status.isTextual() ? status.asText() : "UNKNOWN";

Check failure on line 110 in apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "UNKNOWN" 3 times.

See more on https://sonarcloud.io/project/issues?id=abapify_openadt&issues=AZ6dmP4QAQ80L8fBLFuH&open=AZ6dmP4QAQ80L8fBLFuH&pullRequest=39
}

private String webAdapterStatusV2(String encodedProfileId) throws IOException, InterruptedException {
URI uri = URI.create(hubBaseUrl + "/slc2/api/status?profileid=" + encodedProfileId);
HttpResponse<String> response = httpClient.send(
hubRequest(uri).GET().build(),
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() != 200) {
throw new IOException("Secure Login hub v2 status failed with HTTP " + response.statusCode());
}
JsonNode root = objectMapper.readTree(response.body());
JsonNode status = root.path("status");
return status.isTextual() ? status.asText() : "UNKNOWN";
}

public void loginWebAdapter(String profileId) throws IOException, InterruptedException {
loginWebAdapter(profileId, false);
}

public void loginWebAdapter(String profileId, boolean browserMonitor) throws IOException, InterruptedException {
// Try API v3 first (Windows), fall back to v2 (macOS)
try {
loginWebAdapterV3(profileId, browserMonitor);
} catch (IOException e) {
loginWebAdapterV2(profileId, browserMonitor);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Retain the original v3 IOException in the login fallback path. When the v2 call also throws, the initial v3 failure should be attached (for example as a suppressed exception) so error reporting keeps full context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/openadt-sap-adt/src/main/java/org/openadt/sap/adt/bootstrap/SecureLoginHubClient.java, line 136:

<comment>Retain the original v3 IOException in the login fallback path. When the v2 call also throws, the initial v3 failure should be attached (for example as a suppressed exception) so error reporting keeps full context.</comment>

<file context>
@@ -101,11 +110,34 @@ public String webAdapterStatus(String profileId) throws IOException, Interrupted
+        try {
+            loginWebAdapterV3(profileId, browserMonitor);
+        } catch (IOException e) {
+            loginWebAdapterV2(profileId, browserMonitor);
+        }
+    }
</file context>
Suggested change
loginWebAdapterV2(profileId, browserMonitor);
try {
loginWebAdapterV2(profileId, browserMonitor);
} catch (IOException v2Error) {
v2Error.addSuppressed(e);
throw v2Error;
}

}
}

private void loginWebAdapterV3(String profileId, boolean browserMonitor) throws IOException, InterruptedException {
URI uri = URI.create(hubBaseUrl + "/slc3/api/login");
java.util.Map<String, Object> clientConfig = new java.util.LinkedHashMap<>();
clientConfig.put("browserMonitor", browserMonitor);
Expand All @@ -130,6 +162,31 @@
}
}

private void loginWebAdapterV2(String profileId, boolean browserMonitor) throws IOException, InterruptedException {
URI uri = URI.create(hubBaseUrl + "/slc2/api/login");
java.util.Map<String, Object> clientConfig = new java.util.LinkedHashMap<>();
clientConfig.put("browserMonitor", browserMonitor);
clientConfig.put("inactivityTimeout", 0);
clientConfig.put("keySize", 2048);
clientConfig.put("profileSLWA", profileId);
clientConfig.put("localport", 34443);
clientConfig.put("autoLogOut", false);
java.util.Map<String, Object> payload = new java.util.LinkedHashMap<>();
payload.put("profileid", profileId);
payload.put("clientConfig", clientConfig);
String body = objectMapper.writeValueAsString(payload);
HttpResponse<String> response = httpClient.send(
hubRequest(uri)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build(),
HttpResponse.BodyHandlers.ofString()
);
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IOException("Secure Login hub v2 login failed with HTTP " + response.statusCode());
}
}

public void ensureWebAdapterLoggedIn(String profileId) throws IOException, InterruptedException {
ensureWebAdapterLoggedIn(profileId, false);
}
Expand Down
3 changes: 3 additions & 0 deletions specs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ Lookup paths:

- **Windows**: `%APPDATA%\SAP\Common\SAPUILandscape.xml`
- **macOS**: `~/Library/Application Support/SAP/Common/SAPUILandscape.xml`
- **macOS (SAP GUI for Java)**: `~/Library/Preferences/SAP/SAPGUILandscape.xml`
- **WSL**: `/mnt/c/Users/<user>/AppData/Roaming/SAP/Common/SAPUILandscape.xml`
- **WSL**: `/mnt/c/Users/<user>/AppData/Roaming/SAP/LogonServerConfigCache/*.xml`

When the landscape file contains `<Include url="...">` tags pointing to external landscapes (common with centrally-managed SAP GUI deployments), the detector logs an informational message. Full system details (including message server hostnames) may require opening SAP GUI once to cache the landscape locally, or manual configuration of the `mshost` field.

For classic `<System>` entries, extracts:

- `name` → alias, description
Expand Down