Skip to content

Commit 1af987a

Browse files
authored
fix: Fix npm relative folder and tests (#22857)
- Fix relative path for npm-cli in windows - Validate found alternative node. - Remove validate npm as the faulty version 6.0 should never be installed.
1 parent 8bae282 commit 1af987a

File tree

7 files changed

+244
-346
lines changed

7 files changed

+244
-346
lines changed

flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendTools.java

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -469,44 +469,6 @@ void checkForFaultyNpmVersion(FrontendVersion npmVersion) {
469469
}
470470
}
471471

472-
/**
473-
* Checks whether the currently installed npm version accepts/properly
474-
* processes the path to a given folder.
475-
* <p>
476-
* For example, the older versions of npm don't accept whitespaces in
477-
* folders path.
478-
*
479-
* @param folder
480-
* the folder to check.
481-
* @return <code>true</code>, if the current version of npm accepts the
482-
* given folder path, <code>false</code> if it causes issues.
483-
*/
484-
boolean folderIsAcceptableByNpm(File folder) {
485-
Objects.requireNonNull(folder);
486-
boolean hidden = folder.isHidden()
487-
|| folder.getPath().contains(File.separator + ".");
488-
if (!hidden && (!folder.exists() || !folder.isDirectory())) {
489-
getLogger().warn(
490-
"Failed to check whether npm accepts the folder '{}', because the folder doesn't exist or not a directory",
491-
folder);
492-
return true;
493-
}
494-
495-
if (FrontendUtils.isWindows()
496-
&& folder.getAbsolutePath().matches(".*[\\s+].*")) {
497-
try {
498-
FrontendVersion foundNpmVersion = getNpmVersion();
499-
// npm < 7.0.0 doesn't accept whitespaces in path
500-
return foundNpmVersion
501-
.isEqualOrNewer(WHITESPACE_ACCEPTING_NPM_VERSION);
502-
} catch (UnknownVersionException e) {
503-
getLogger().warn("Error checking if npm accepts path '{}'",
504-
folder, e);
505-
}
506-
}
507-
return true;
508-
}
509-
510472
/**
511473
* Gives a file object representing path to the cache directory of currently
512474
* installed npm.

flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeResolver.java

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,14 @@ private String getGlobalNpmCliScript(File nodeExecutable) {
221221
boolean isWindows = FrontendUtils.isWindows();
222222

223223
// Try common locations relative to node executable
224-
String[] possiblePaths = isWindows
225-
? new String[] { "..\\node_modules\\npm\\bin\\npm-cli.js" }
226-
: new String[] { "../lib/node_modules/npm/bin/npm-cli.js" };
227-
228-
for (String path : possiblePaths) {
229-
File npmCliScript = new File(nodeDir, path);
230-
if (npmCliScript.exists()) {
231-
return npmCliScript.getAbsolutePath();
232-
}
224+
// *nix machines have the executable under node/bin folder,
225+
// but windows has it in the node root folder
226+
String path = isWindows ? "node_modules\\npm\\bin\\npm-cli.js"
227+
: "../lib/node_modules/npm/bin/npm-cli.js";
228+
229+
File npmCliScript = new File(nodeDir, path);
230+
if (npmCliScript.exists()) {
231+
return npmCliScript.getAbsolutePath();
233232
}
234233

235234
return null;
@@ -293,8 +292,31 @@ private ActiveNodeInstallation resolveOrInstallAlternativeNode() {
293292
versionToUse = fallbackVersion;
294293
nodeExecutable = getNodeExecutableForVersion(alternativeDirFile,
295294
versionToUse);
296-
return createActiveInstallation(nodeExecutable, versionToUse,
297-
alternativeDirFile);
295+
296+
// Also validate that found alternative actually works.
297+
try {
298+
String installedVersion = FrontendUtils
299+
.getVersion("node", List.of(
300+
nodeExecutable.getAbsolutePath(), "--version"))
301+
.getFullVersion();
302+
303+
// Normalize versions for comparison
304+
String normalizedInstalled = installedVersion.startsWith("v")
305+
? installedVersion.substring(1)
306+
: installedVersion;
307+
String normalizedRequested = nodeVersion.startsWith("v")
308+
? nodeVersion.substring(1)
309+
: nodeVersion;
310+
311+
if (normalizedInstalled.equals(normalizedRequested)) {
312+
return createActiveInstallation(nodeExecutable,
313+
versionToUse, alternativeDirFile);
314+
}
315+
} catch (UnknownVersionException e) {
316+
getLogger().debug(
317+
"Could not verify version of existing node installation",
318+
e);
319+
}
298320
}
299321

300322
// No suitable version found, install the requested version

flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ private void runNpmInstall() throws ExecutionFailedException {
255255
if (options.isEnableBun()) {
256256
npmExecutable = tools.getBunExecutable();
257257
} else if (options.isEnablePnpm()) {
258-
validateInstalledNpm(tools);
259258
npmExecutable = tools.getPnpmExecutable();
260259
} else {
261260
npmExecutable = tools.getNpmExecutable();
@@ -610,21 +609,4 @@ private void deleteNodeModules(File nodeModulesFolder)
610609
"Exception removing node_modules. Please remove it manually.");
611610
}
612611
}
613-
614-
private void validateInstalledNpm(FrontendTools tools)
615-
throws IllegalStateException {
616-
File npmCacheDir = null;
617-
try {
618-
npmCacheDir = tools.getNpmCacheDir();
619-
} catch (FrontendUtils.CommandExecutionException
620-
| IllegalStateException e) {
621-
packageUpdater.log().warn("Failed to get npm cache directory", e);
622-
}
623-
624-
if (npmCacheDir != null
625-
&& !tools.folderIsAcceptableByNpm(npmCacheDir)) {
626-
throw new IllegalStateException(
627-
String.format(NPM_VALIDATION_FAIL_MESSAGE));
628-
}
629-
}
630612
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.server.frontend;
17+
18+
import java.io.File;
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
/**
23+
* FrontendTools class fot manually testing NodeJS installation.
24+
*/
25+
public class FrontendToolsExecutor {
26+
27+
/**
28+
* Manual testing utility to demonstrate which Node.js installation will be
29+
* used.
30+
* <p>
31+
* The resolution logic uses any installed Node.js >= minimum supported
32+
* version (v24.0.0). If no suitable installation exists, it installs the
33+
* preferred version specified by -DnodeVersion.
34+
* <p>
35+
* Usage examples:
36+
* <ul>
37+
* <li>Test with global node: {@code mvn exec:java
38+
* -Dexec.mainClass="com.vaadin.flow.server.frontend.FrontendToolsTest"
39+
* -Dexec.classpathScope=test}</li>
40+
* <li>Test forcing alternative: {@code mvn exec:java ...
41+
* -Dalternative=true}</li>
42+
* <li>Test with custom preferred version: {@code mvn exec:java ...
43+
* -DnodeVersion=v24.5.0}</li>
44+
* </ul>
45+
*
46+
* @param args
47+
* command line arguments (not used)
48+
*/
49+
public static void main(String[] args) {
50+
System.out.println("=".repeat(80));
51+
System.out.println("Node.js Resolution Test");
52+
System.out.println("=".repeat(80));
53+
54+
try {
55+
// Read configuration from system properties
56+
boolean forceAlternative = Boolean.getBoolean("alternative");
57+
String preferredVersion = System.getProperty("nodeVersion",
58+
FrontendTools.DEFAULT_NODE_VERSION);
59+
String baseDir = System.getProperty("baseDir",
60+
System.getProperty("user.dir"));
61+
62+
System.out.println("\nConfiguration:");
63+
System.out.println(" Base directory: " + baseDir);
64+
System.out.println(" Supported version for global: >= "
65+
+ FrontendTools.SUPPORTED_NODE_VERSION.getFullVersion());
66+
System.out
67+
.println(" Minimum auto-installed version (~/.vaadin): >= "
68+
+ FrontendTools.MINIMUM_AUTO_INSTALLED_NODE
69+
.getFullVersion());
70+
System.out.println(" Maximum major version: "
71+
+ FrontendTools.MAX_SUPPORTED_NODE_MAJOR_VERSION);
72+
System.out.println(" Preferred version (to install if needed): "
73+
+ preferredVersion);
74+
System.out.println(" Force alternative node: " + forceAlternative);
75+
System.out.println();
76+
77+
// Create FrontendTools instance
78+
FrontendToolsSettings settings = new FrontendToolsSettings(baseDir,
79+
() -> FrontendUtils.getVaadinHomeDirectory()
80+
.getAbsolutePath());
81+
settings.setNodeVersion(preferredVersion);
82+
settings.setForceAlternativeNode(forceAlternative);
83+
84+
FrontendTools tools = new FrontendTools(settings);
85+
86+
// Get resolved node information
87+
String nodeExecutable = tools.getNodeExecutable();
88+
String actualVersionUsed = tools.getNodeVersion().getFullVersion();
89+
String npmVersion = tools.getNpmVersion().getFullVersion();
90+
91+
System.out.println("Resolved Node.js installation:");
92+
System.out.println(" Node executable: " + nodeExecutable);
93+
System.out.println(" Actual version used: " + actualVersionUsed);
94+
System.out.println(" npm version: " + npmVersion);
95+
96+
// Check if using global or alternative installation
97+
File nodeFile = new File(nodeExecutable);
98+
boolean isGlobal = !nodeFile.getAbsolutePath()
99+
.contains(FrontendUtils.getVaadinHomeDirectory().getName());
100+
101+
System.out.println("\nInstallation type: "
102+
+ (isGlobal ? "GLOBAL" : "ALTERNATIVE (~/.vaadin)"));
103+
104+
if (!isGlobal) {
105+
System.out.println(" Location: " + FrontendUtils
106+
.getVaadinHomeDirectory().getAbsolutePath());
107+
}
108+
109+
// Try to run node --version to verify it works
110+
System.out.println("\nVerification:");
111+
try {
112+
List<String> versionCommand = new ArrayList<>();
113+
versionCommand.add(nodeExecutable);
114+
versionCommand.add("--version");
115+
FrontendVersion version = FrontendUtils.getVersion("node",
116+
versionCommand);
117+
System.out.println(" ✓ Node executable is working");
118+
System.out.println(
119+
" ✓ Verified version: " + version.getFullVersion());
120+
} catch (Exception e) {
121+
System.out.println(" ✗ Failed to verify node executable: "
122+
+ e.getMessage());
123+
}
124+
125+
System.out.println("\n" + "=".repeat(80));
126+
System.out.println("Resolution completed successfully");
127+
System.out.println("=".repeat(80));
128+
129+
} catch (Exception e) {
130+
System.err.println("\n" + "=".repeat(80));
131+
System.err.println("ERROR: Resolution failed");
132+
System.err.println("=".repeat(80));
133+
System.err.println("\nException: " + e.getClass().getName());
134+
System.err.println("Message: " + e.getMessage());
135+
System.err.println("\nStack trace:");
136+
e.printStackTrace(System.err);
137+
System.exit(1);
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)