diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java
index 1b060a7e0b1..06fc9dd68a7 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java
@@ -659,12 +659,12 @@ private boolean doStartWebpack(ApplicationConfiguration config,
ProcessBuilder processBuilder = new ProcessBuilder()
.directory(npmFolder);
- FrontendTools tools = new FrontendTools(npmFolder.getAbsolutePath(),
- () -> FrontendUtils.getVaadinHomeDirectory().getAbsolutePath());
- tools.validateNodeAndNpmVersion();
-
boolean useHomeNodeExec = config.getBooleanProperty(
InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false);
+ FrontendTools tools = new FrontendTools(npmFolder.getAbsolutePath(),
+ () -> FrontendUtils.getVaadinHomeDirectory().getAbsolutePath(),
+ useHomeNodeExec);
+ tools.validateNodeAndNpmVersion();
String nodeExec = null;
if (useHomeNodeExec) {
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendTools.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendTools.java
index 3beac78f22c..124c5759898 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendTools.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendTools.java
@@ -25,6 +25,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -154,6 +155,33 @@ String getScript() {
private final boolean ignoreVersionChecks;
+ private final boolean forceAlternativeNode;
+
+ /**
+ * Creates an instance of the class using the {@code baseDir} as a base
+ * directory to locate the tools and the directory returned by the
+ * {@code alternativeDirGetter} as a directory to install tools if they are
+ * not found and use it as an alternative tools location.
+ *
+ * If {@code alternativeDir} is {@code null} tools won't be installed.
+ *
+ *
+ * @param baseDir
+ * the base directory to locate the tools, not {@code null}
+ * @param alternativeDirGetter
+ * the getter for a directory where tools will be installed if
+ * they are not found globally or in the {@code baseDir}, may be
+ * {@code null}
+ * @param forceAlternativeNode
+ * force usage of node executable from alternative directory
+ */
+ public FrontendTools(String baseDir, Supplier alternativeDirGetter,
+ boolean forceAlternativeNode) {
+ this(baseDir, alternativeDirGetter, DEFAULT_NODE_VERSION,
+ URI.create(NodeInstaller.DEFAULT_NODEJS_DOWNLOAD_ROOT),
+ forceAlternativeNode);
+ }
+
/**
* Creates an instance of the class using the {@code baseDir} as a base
* directory to locate the tools and the directory returned by the
@@ -173,7 +201,7 @@ String getScript() {
public FrontendTools(String baseDir,
Supplier alternativeDirGetter) {
this(baseDir, alternativeDirGetter, DEFAULT_NODE_VERSION,
- URI.create(NodeInstaller.DEFAULT_NODEJS_DOWNLOAD_ROOT));
+ URI.create(NodeInstaller.DEFAULT_NODEJS_DOWNLOAD_ROOT), false);
}
/**
@@ -200,22 +228,58 @@ public FrontendTools(String baseDir,
* corporate environments where the node.js download can be
* provided from an intranet mirror. Use
* {@link NodeInstaller#DEFAULT_NODEJS_DOWNLOAD_ROOT} by default.
+ * @param forceAlternativeNode
+ * force usage of node executable from alternative directory
*/
public FrontendTools(String baseDir, Supplier alternativeDirGetter,
- String nodeVersion, URI nodeDownloadRoot) {
+ String nodeVersion, URI nodeDownloadRoot,
+ boolean forceAlternativeNode) {
this(baseDir, alternativeDirGetter, nodeVersion, nodeDownloadRoot,
"true".equalsIgnoreCase(System.getProperty(
- FrontendUtils.PARAM_IGNORE_VERSION_CHECKS)));
+ FrontendUtils.PARAM_IGNORE_VERSION_CHECKS)),
+ forceAlternativeNode);
+ }
+
+ /**
+ * Creates an instance of the class using the {@code baseDir} as a base
+ * directory to locate the tools and the directory returned by the
+ * {@code alternativeDirGetter} as a directory to install tools if they are
+ * not found and use it as an alternative tools location.
+ *
+ * If {@code alternativeDir} is {@code null} tools won't be installed.
+ *
+ *
+ * @param baseDir
+ * the base directory to locate the tools, not {@code null}
+ * @param alternativeDirGetter
+ * the getter for a directory where tools will be installed if
+ * they are not found globally or in the {@code baseDir}, may be
+ * {@code null}
+ * @param nodeVersion
+ * The node.js version to be used when node.js is installed
+ * automatically by Vaadin, for example "v14.15.4"
.
+ * Use {@value #DEFAULT_NODE_VERSION} by default.
+ * @param nodeDownloadRoot
+ * Download node.js from this URL. Handy in heavily firewalled
+ * corporate environments where the node.js download can be
+ * provided from an intranet mirror. Use
+ * {@link NodeInstaller#DEFAULT_NODEJS_DOWNLOAD_ROOT} by default.
+ */
+ public FrontendTools(String baseDir, Supplier alternativeDirGetter,
+ String nodeVersion, URI nodeDownloadRoot) {
+ this(baseDir, alternativeDirGetter, nodeVersion, nodeDownloadRoot,
+ false);
}
FrontendTools(String baseDir, Supplier alternativeDirGetter,
String nodeVersion, URI nodeDownloadRoot,
- boolean ignoreVersionChecks) {
+ boolean ignoreVersionChecks, boolean forceAlternativeNode) {
this.baseDir = Objects.requireNonNull(baseDir);
this.alternativeDirGetter = alternativeDirGetter;
this.nodeVersion = Objects.requireNonNull(nodeVersion);
this.nodeDownloadRoot = Objects.requireNonNull(nodeDownloadRoot);
this.ignoreVersionChecks = ignoreVersionChecks;
+ this.forceAlternativeNode = forceAlternativeNode;
}
/**
@@ -225,8 +289,23 @@ public FrontendTools(String baseDir, Supplier alternativeDirGetter,
*/
public String getNodeExecutable() {
Pair nodeCommands = getNodeCommands();
- return getExecutable(nodeCommands.getFirst(), nodeCommands.getSecond(),
- alternativeDirGetter != null).getAbsolutePath();
+ File file = getExecutable(baseDir, nodeCommands.getSecond());
+ if (file == null) {
+ file = frontendToolsLocator.tryLocateTool(nodeCommands.getFirst())
+ .orElse(null);
+ }
+ if (file == null) {
+ file = getExecutable(getAlternativeDir(), nodeCommands.getSecond());
+ }
+ if (file == null && alternativeDirGetter != null) {
+ getLogger().info("Couldn't find {}. Installing Node and NPM to {}.",
+ nodeCommands.getFirst(), getAlternativeDir());
+ file = new File(installNode(nodeVersion, nodeDownloadRoot));
+ }
+ if (file == null) {
+ throw new IllegalStateException(String.format(NODE_NOT_FOUND));
+ }
+ return file.getAbsolutePath();
}
/**
@@ -299,7 +378,7 @@ public void validateNodeAndNpmVersion() {
}
try {
List nodeVersionCommand = new ArrayList<>();
- nodeVersionCommand.add(getNodeExecutable());
+ nodeVersionCommand.add(doGetNodeExecutable());
nodeVersionCommand.add("--version"); // NOSONAR
FrontendVersion foundNodeVersion = FrontendUtils.getVersion("node",
nodeVersionCommand);
@@ -385,28 +464,12 @@ void checkForFaultyNpmVersion(FrontendVersion npmVersion) {
}
}
- private File getExecutable(String cmd, String defaultLocation,
- boolean installNode) {
- File file = null;
- if (defaultLocation == null) {
- file = frontendToolsLocator.tryLocateTool(cmd).orElse(null);
- } else {
- file = Arrays.asList(() -> baseDir, alternativeDirGetter).stream()
- .map(Supplier::get)
- .map(dir -> new File(dir, defaultLocation))
- .filter(frontendToolsLocator::verifyTool).findFirst()
- .orElseGet(() -> frontendToolsLocator.tryLocateTool(cmd)
- .orElse(null));
- }
- if (file == null && installNode) {
- getLogger().info("Couldn't find {}. Installing Node and NPM to {}.",
- cmd, getAlternativeDir());
- return new File(installNode(nodeVersion, nodeDownloadRoot));
+ private File getExecutable(String dir, String location) {
+ File file = new File(dir, location);
+ if (frontendToolsLocator.verifyTool(file)) {
+ return file;
}
- if (file == null) {
- throw new IllegalStateException(String.format(NODE_NOT_FOUND));
- }
- return file;
+ return null;
}
private Pair getNodeCommands() {
@@ -550,17 +613,30 @@ private List getNpmCliToolExecutable(NpmCliTool cliTool,
// First look for *-cli.js script in project/node_modules
List returnCommand = getNpmScriptCommand(baseDir,
cliTool.getScript());
- if (returnCommand.isEmpty()) {
+ boolean alternativeDirChecked = false;
+ if (returnCommand.isEmpty() && forceAlternativeNode) {
// First look for *-cli.js script in ~/.vaadin/node/node_modules
+ // only if alternative node takes precedence over all other location
returnCommand = getNpmScriptCommand(getAlternativeDir(),
cliTool.getScript());
+ alternativeDirChecked = true;
}
if (returnCommand.isEmpty()) {
// Otherwise look for regular `npm`/`npx` global search path
- returnCommand = Collections.singletonList(
- getExecutable(cliTool.getCommand(), null, true)
- .getAbsolutePath());
+ Optional command = frontendToolsLocator
+ .tryLocateTool(cliTool.getCommand())
+ .map(File::getAbsolutePath);
+ if (command.isPresent()) {
+ returnCommand = Collections.singletonList(command.get());
+ }
}
+ if (!alternativeDirChecked && returnCommand.isEmpty()) {
+ // Use alternative if global is not found and alternative location
+ // is not yet checked
+ returnCommand = getNpmScriptCommand(getAlternativeDir(),
+ cliTool.getScript());
+ }
+
if (flags.length > 0) {
returnCommand = new ArrayList<>(returnCommand);
Collections.addAll(returnCommand, flags);
@@ -576,7 +652,7 @@ private List getNpmScriptCommand(String dir, String scriptName) {
List returnCommand = new ArrayList<>();
if (file.canRead()) {
// We return a two element list with node binary and npm-cli script
- returnCommand.add(getNodeExecutable());
+ returnCommand.add(doGetNodeExecutable());
returnCommand.add(file.getAbsolutePath());
}
return returnCommand;
@@ -639,4 +715,12 @@ private String buildBadVersionString(String tool, String version,
private String getAlternativeDir() {
return alternativeDirGetter.get();
}
+
+ private String doGetNodeExecutable() {
+ if (forceAlternativeNode) {
+ return forceAlternativeNodeExecutable();
+ } else {
+ return getNodeExecutable();
+ }
+ }
}
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java
index 039ed4f5ad8..7c50a1478b1 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java
@@ -375,7 +375,7 @@ private void runNpmInstall() throws ExecutionFailedException {
FrontendTools tools = new FrontendTools(baseDir,
() -> FrontendUtils.getVaadinHomeDirectory().getAbsolutePath(),
- nodeVersion, nodeDownloadRoot);
+ nodeVersion, nodeDownloadRoot, requireHomeNodeExec);
try {
if (requireHomeNodeExec) {
tools.forceAlternativeNodeExecutable();
diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendToolsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendToolsTest.java
index 287ec1299a0..4fb277dd763 100644
--- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendToolsTest.java
+++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendToolsTest.java
@@ -190,12 +190,17 @@ public void installNodeFromFileSystem_ForceAlternativeNodeExecutableInstallsToTa
}
@Test
- public void should_useSystemNode() {
+ public void homeNodeIsNotForced_useGlobalNode() throws IOException {
+ createStubNode(true, true, vaadinHomeDir);
+
assertThat(tools.getNodeExecutable(), containsString("node"));
assertThat(tools.getNodeExecutable(),
not(containsString(DEFAULT_NODE)));
assertThat(tools.getNodeExecutable(),
not(containsString(NPM_CLI_STRING)));
+ assertThat(tools.getNodeExecutable(),
+ not(containsString(vaadinHomeDir)));
+ assertThat(tools.getNodeExecutable(), not(containsString(baseDir)));
assertEquals(4, tools.getNpmExecutable().size());
assertThat(tools.getNpmExecutable().get(0), containsString("npm"));
@@ -433,14 +438,6 @@ public void should_useProjectNodeFirst() throws Exception {
assertNodeCommand(() -> baseDir);
}
- @Test
- public void should_useHomeFirst() throws Exception {
- Assume.assumeFalse(
- "Skipping test on windows until a fake node.exe that isn't caught by Window defender can be created.",
- FrontendUtils.isWindows());
- assertNodeCommand(() -> vaadinHomeDir);
- }
-
@Test
public void should_useProjectNpmFirst() throws Exception {
Assume.assumeFalse(
@@ -452,10 +449,13 @@ public void should_useProjectNpmFirst() throws Exception {
}
@Test
- public void should_useHomeNpmFirst() throws Exception {
+ public void forceHomeNode_useHomeNpmFirst() throws Exception {
Assume.assumeFalse(
"Skipping test on windows until a fake node.exe that isn't caught by Window defender can be created.",
FrontendUtils.isWindows());
+ tools = new FrontendTools(baseDir, () -> vaadinHomeDir, true);
+
+ createStubNode(true, true, vaadinHomeDir);
assertNpmCommand(() -> vaadinHomeDir);
}
@@ -485,7 +485,7 @@ public void getSuitablePnpm_tooOldVersionInstalledAndSkipVersionCheck_accepted()
throws Exception {
tools = new FrontendTools(baseDir, () -> vaadinHomeDir,
FrontendTools.DEFAULT_NODE_VERSION, new File(baseDir).toURI(),
- true);
+ true, false);
Assume.assumeFalse(tools.getNodeExecutable().isEmpty());
createStubNode(false, true, baseDir);
createFakePnpm("4.5.0");