diff --git a/frontend-maven-plugin/src/it/node-version-from-engines/package.json b/frontend-maven-plugin/src/it/node-version-from-engines/package.json
new file mode 100644
index 000000000..1f129e6f9
--- /dev/null
+++ b/frontend-maven-plugin/src/it/node-version-from-engines/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "example",
+ "version": "0.0.1",
+ "engines": {
+ "node": ">=10.3 <15"
+ },
+ "dependencies": {
+ "less": "~3.0.2"
+ },
+ "scripts": {
+ "prebuild": "npm install"
+ }
+}
diff --git a/frontend-maven-plugin/src/it/node-version-from-engines/pom.xml b/frontend-maven-plugin/src/it/node-version-from-engines/pom.xml
new file mode 100644
index 000000000..fc8490857
--- /dev/null
+++ b/frontend-maven-plugin/src/it/node-version-from-engines/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+ com.github.eirslett
+ example
+ 0
+ pom
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+ @project.version@
+
+
+ target
+
+
+
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+ engines
+
+
+
+
+ npm install
+
+ npm
+
+
+
+ install
+
+
+
+
+
+
+
+
diff --git a/frontend-maven-plugin/src/it/node-version-from-engines/verify.groovy b/frontend-maven-plugin/src/it/node-version-from-engines/verify.groovy
new file mode 100644
index 000000000..0a9d23ea4
--- /dev/null
+++ b/frontend-maven-plugin/src/it/node-version-from-engines/verify.groovy
@@ -0,0 +1,9 @@
+assert new File(basedir, 'target/node').exists() : "Node was not installed in the custom install directory";
+assert new File(basedir, 'node_modules').exists() : "Node modules were not installed in the base directory";
+assert new File(basedir, 'target/node/npm').exists() : "npm was not copied to the node directory";
+
+import org.codehaus.plexus.util.FileUtils;
+
+String buildLog = FileUtils.fileRead(new File(basedir, 'build.log'));
+
+assert buildLog.contains('BUILD SUCCESS') : 'build was not successful'
diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndNpmMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndNpmMojo.java
index c80b3c0a5..c163ff42e 100644
--- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndNpmMojo.java
+++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndNpmMojo.java
@@ -78,7 +78,7 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
String npmDownloadRoot = getNpmDownloadRoot();
Server server = MojoUtils.decryptServer(serverId, session, decrypter);
if (null != server) {
- factory.getNodeInstaller(proxyConfig)
+ String installedNodeVersion=factory.getNodeInstaller(proxyConfig)
.setNodeVersion(nodeVersion)
.setNodeDownloadRoot(nodeDownloadRoot)
.setNpmVersion(npmVersion)
@@ -86,20 +86,20 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
.setPassword(server.getPassword())
.install();
factory.getNPMInstaller(proxyConfig)
- .setNodeVersion(nodeVersion)
+ .setNodeVersion(installedNodeVersion)
.setNpmVersion(npmVersion)
.setNpmDownloadRoot(npmDownloadRoot)
.setUserName(server.getUsername())
.setPassword(server.getPassword())
.install();
} else {
- factory.getNodeInstaller(proxyConfig)
+ String installedNodeVersion=factory.getNodeInstaller(proxyConfig)
.setNodeVersion(nodeVersion)
.setNodeDownloadRoot(nodeDownloadRoot)
.setNpmVersion(npmVersion)
.install();
factory.getNPMInstaller(proxyConfig)
- .setNodeVersion(this.nodeVersion)
+ .setNodeVersion(installedNodeVersion)
.setNpmVersion(this.npmVersion)
.setNpmDownloadRoot(npmDownloadRoot)
.install();
diff --git a/frontend-plugin-core/pom.xml b/frontend-plugin-core/pom.xml
index 0c69b62cb..4e6298504 100644
--- a/frontend-plugin-core/pom.xml
+++ b/frontend-plugin-core/pom.xml
@@ -11,6 +11,12 @@
jar
+
+ com.vdurmont
+ semver4j
+ 3.1.0
+
+
com.fasterxml.jackson.core
jackson-core
diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeInstaller.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeInstaller.java
index 805c8901c..345cbfcd3 100644
--- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeInstaller.java
+++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeInstaller.java
@@ -7,7 +7,13 @@
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
-
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vdurmont.semver4j.Requirement;
+import com.vdurmont.semver4j.Semver;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,6 +34,8 @@ public class NodeInstaller {
private final FileDownloader fileDownloader;
+ private Requirement nodeVersionRequirement;
+
NodeInstaller(InstallConfig config, ArchiveExtractor archiveExtractor, FileDownloader fileDownloader) {
this.logger = LoggerFactory.getLogger(getClass());
this.config = config;
@@ -74,13 +82,65 @@ private boolean npmProvided() throws InstallationException {
return false;
}
- public void install() throws InstallationException {
+ public String install() throws InstallationException {
// use static lock object for a synchronized block
synchronized (LOCK) {
if (this.nodeDownloadRoot == null || this.nodeDownloadRoot.isEmpty()) {
this.nodeDownloadRoot = this.config.getPlatform().getNodeDownloadRoot();
}
+
+ if ("engines".equals(this.nodeVersion)) {
+ try {
+ File packageFile = new File(this.config.getWorkingDirectory(), "package.json");
+ HashMap data = new ObjectMapper().readValue(packageFile, HashMap.class);
+ if (data.containsKey("engines")) {
+ HashMap engines = (HashMap) data.get("engines");
+ if (engines.containsKey("node")) {
+ this.nodeVersionRequirement = Requirement.buildNPM((String) engines.get("node"));
+ } else {
+ this.logger.info("Could not read node from engines from package.json");
+ }
+ } else {
+ this.logger.info("Could not read engines from package.json");
+ }
+ } catch (IOException e) {
+ throw new InstallationException("Could not read node engine version from package.json", e);
+ }
+ }
+
if (!nodeIsAlreadyInstalled()) {
+ if (this.nodeVersionRequirement != null) {
+ // download available node versions
+ try {
+ String downloadUrl = this.nodeDownloadRoot
+ + "index.json";
+
+ File tmpDirectory = getTempDirectory();
+
+ File archive = File.createTempFile("node_versions", ".json", tmpDirectory);
+
+ downloadFile(downloadUrl, archive, this.userName, this.password);
+
+ HashMap[] data = new ObjectMapper().readValue(archive, HashMap[].class);
+
+ List nodeVersions = new LinkedList<>();
+ for (HashMap d : data) {
+ if (d.containsKey("version")) {
+ nodeVersions.add((String) d.get("version"));
+ }
+ }
+
+ // we want the oldest possible version, that satisfies the requirements
+ Collections.reverse(nodeVersions);
+
+ logger.debug("Available node versions: {}", nodeVersions);
+ this.nodeVersion = nodeVersions.stream().filter(version -> nodeVersionRequirement.isSatisfiedBy(new Semver(version, Semver.SemverType.NPM))).findFirst().orElseThrow(() -> new InstallationException("Could not find matching node version satisfying requirement " + this.nodeVersionRequirement));
+ this.logger.info("Found matching node version {} satisfying requirement {}.", this.nodeVersion, this.nodeVersionRequirement);
+ } catch (IOException | DownloadException e) {
+ throw new InstallationException("Could not get available node versions.", e);
+ }
+ }
+
this.logger.info("Installing node version {}", this.nodeVersion);
if (!this.nodeVersion.startsWith("v")) {
this.logger.warn("Node version does not start with naming convention 'v'.");
@@ -96,6 +156,8 @@ public void install() throws InstallationException {
}
}
}
+
+ return nodeVersion;
}
private boolean nodeIsAlreadyInstalled() {
@@ -104,14 +166,19 @@ private boolean nodeIsAlreadyInstalled() {
File nodeFile = executorConfig.getNodePath();
if (nodeFile.exists()) {
final String version =
- new NodeExecutor(executorConfig, Arrays.asList("--version"), null).executeAndGetResult(logger);
+ new NodeExecutor(executorConfig, Arrays.asList("--version"), null).executeAndGetResult(logger);
- if (version.equals(this.nodeVersion)) {
+ if (nodeVersionRequirement != null && nodeVersionRequirement.isSatisfiedBy(new Semver(version, Semver.SemverType.NPM))) {
+ //update version with installed version
+ this.nodeVersion = version;
+ this.logger.info("Node {} matches required version range {} installed.", version, nodeVersionRequirement);
+ return true;
+ } else if (version.equals(this.nodeVersion)) {
this.logger.info("Node {} is already installed.", version);
return true;
} else {
this.logger.info("Node {} was installed, but we need version {}", version,
- this.nodeVersion);
+ this.nodeVersion);
return false;
}
} else {