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 {