Skip to content

Commit 987d474

Browse files
committed
support installing node version from package.json engines
fixes #798
1 parent 4d13f04 commit 987d474

File tree

6 files changed

+155
-9
lines changed

6 files changed

+155
-9
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "example",
3+
"version": "0.0.1",
4+
"engines": {
5+
"node": ">=10.3 <15"
6+
},
7+
"dependencies": {
8+
"less": "~3.0.2"
9+
},
10+
"scripts": {
11+
"prebuild": "npm install"
12+
}
13+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.github.eirslett</groupId>
6+
<artifactId>example</artifactId>
7+
<version>0</version>
8+
<packaging>pom</packaging>
9+
10+
<build>
11+
<plugins>
12+
<plugin>
13+
<groupId>com.github.eirslett</groupId>
14+
<artifactId>frontend-maven-plugin</artifactId>
15+
<!-- NB! Set <version> to the latest released version of frontend-maven-plugin, like in README.md -->
16+
<version>@project.version@</version>
17+
18+
<configuration>
19+
<installDirectory>target</installDirectory>
20+
</configuration>
21+
22+
<executions>
23+
24+
<execution>
25+
<id>install node and npm</id>
26+
<goals>
27+
<goal>install-node-and-npm</goal>
28+
</goals>
29+
<configuration>
30+
<nodeVersion>engines</nodeVersion>
31+
</configuration>
32+
</execution>
33+
34+
<execution>
35+
<id>npm install</id>
36+
<goals>
37+
<goal>npm</goal>
38+
</goals>
39+
<!-- Optional configuration which provides for running any npm command -->
40+
<configuration>
41+
<arguments>install</arguments>
42+
</configuration>
43+
</execution>
44+
45+
</executions>
46+
</plugin>
47+
</plugins>
48+
</build>
49+
</project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
assert new File(basedir, 'target/node').exists() : "Node was not installed in the custom install directory";
2+
assert new File(basedir, 'node_modules').exists() : "Node modules were not installed in the base directory";
3+
assert new File(basedir, 'target/node/npm').exists() : "npm was not copied to the node directory";
4+
5+
import org.codehaus.plexus.util.FileUtils;
6+
7+
String buildLog = FileUtils.fileRead(new File(basedir, 'build.log'));
8+
9+
assert buildLog.contains('BUILD SUCCESS') : 'build was not successful'

frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndNpmMojo.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,28 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
7878
String npmDownloadRoot = getNpmDownloadRoot();
7979
Server server = MojoUtils.decryptServer(serverId, session, decrypter);
8080
if (null != server) {
81-
factory.getNodeInstaller(proxyConfig)
81+
String installedNodeVersion=factory.getNodeInstaller(proxyConfig)
8282
.setNodeVersion(nodeVersion)
8383
.setNodeDownloadRoot(nodeDownloadRoot)
8484
.setNpmVersion(npmVersion)
8585
.setUserName(server.getUsername())
8686
.setPassword(server.getPassword())
8787
.install();
8888
factory.getNPMInstaller(proxyConfig)
89-
.setNodeVersion(nodeVersion)
89+
.setNodeVersion(installedNodeVersion)
9090
.setNpmVersion(npmVersion)
9191
.setNpmDownloadRoot(npmDownloadRoot)
9292
.setUserName(server.getUsername())
9393
.setPassword(server.getPassword())
9494
.install();
9595
} else {
96-
factory.getNodeInstaller(proxyConfig)
96+
String installedNodeVersion=factory.getNodeInstaller(proxyConfig)
9797
.setNodeVersion(nodeVersion)
9898
.setNodeDownloadRoot(nodeDownloadRoot)
9999
.setNpmVersion(npmVersion)
100100
.install();
101101
factory.getNPMInstaller(proxyConfig)
102-
.setNodeVersion(this.nodeVersion)
102+
.setNodeVersion(installedNodeVersion)
103103
.setNpmVersion(this.npmVersion)
104104
.setNpmDownloadRoot(npmDownloadRoot)
105105
.install();

frontend-plugin-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
<packaging>jar</packaging>
1212

1313
<dependencies>
14+
<dependency>
15+
<groupId>com.vdurmont</groupId>
16+
<artifactId>semver4j</artifactId>
17+
<version>3.1.0</version>
18+
</dependency>
19+
1420
<dependency>
1521
<groupId>com.fasterxml.jackson.core</groupId>
1622
<artifactId>jackson-core</artifactId>

frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeInstaller.java

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77
import java.nio.file.Files;
88
import java.nio.file.StandardCopyOption;
99
import java.util.Arrays;
10-
10+
import java.util.Collections;
11+
import java.util.HashMap;
12+
import java.util.LinkedList;
13+
import java.util.List;
14+
import java.util.stream.Stream;
15+
16+
import com.fasterxml.jackson.databind.ObjectMapper;
17+
import com.vdurmont.semver4j.Requirement;
18+
import com.vdurmont.semver4j.Semver;
1119
import org.apache.commons.io.FileUtils;
1220
import org.slf4j.Logger;
1321
import org.slf4j.LoggerFactory;
@@ -28,6 +36,8 @@ public class NodeInstaller {
2836

2937
private final FileDownloader fileDownloader;
3038

39+
private Requirement nodeVersionRequirement;
40+
3141
NodeInstaller(InstallConfig config, ArchiveExtractor archiveExtractor, FileDownloader fileDownloader) {
3242
this.logger = LoggerFactory.getLogger(getClass());
3343
this.config = config;
@@ -74,13 +84,65 @@ private boolean npmProvided() throws InstallationException {
7484
return false;
7585
}
7686

77-
public void install() throws InstallationException {
87+
public String install() throws InstallationException {
7888
// use static lock object for a synchronized block
7989
synchronized (LOCK) {
8090
if (this.nodeDownloadRoot == null || this.nodeDownloadRoot.isEmpty()) {
8191
this.nodeDownloadRoot = this.config.getPlatform().getNodeDownloadRoot();
8292
}
93+
94+
if ("engines".equals(this.nodeVersion)) {
95+
try {
96+
File packageFile = new File(this.config.getWorkingDirectory(), "package.json");
97+
HashMap<String, Object> data = new ObjectMapper().readValue(packageFile, HashMap.class);
98+
if (data.containsKey("engines")) {
99+
HashMap<String, Object> engines = (HashMap<String, Object>) data.get("engines");
100+
if (engines.containsKey("node")) {
101+
this.nodeVersionRequirement = Requirement.buildNPM((String) engines.get("node"));
102+
} else {
103+
this.logger.info("Could not read node from engines from package.json");
104+
}
105+
} else {
106+
this.logger.info("Could not read engines from package.json");
107+
}
108+
} catch (IOException e) {
109+
throw new InstallationException("Could not read node engine version from package.json", e);
110+
}
111+
}
112+
83113
if (!nodeIsAlreadyInstalled()) {
114+
if (this.nodeVersionRequirement != null) {
115+
// download available node versions
116+
try {
117+
String downloadUrl = this.nodeDownloadRoot
118+
+ "index.json";
119+
120+
File tmpDirectory = getTempDirectory();
121+
122+
File archive = File.createTempFile("node_versions", ".json", tmpDirectory);
123+
124+
downloadFile(downloadUrl, archive, this.userName, this.password);
125+
126+
HashMap<String, Object>[] data = new ObjectMapper().readValue(archive, HashMap[].class);
127+
128+
List<String> nodeVersions = new LinkedList<>();
129+
for (HashMap<String, Object> d : data) {
130+
if (d.containsKey("version")) {
131+
nodeVersions.add((String) d.get("version"));
132+
}
133+
}
134+
135+
// we want the oldest possible version, that satisfies the requirements
136+
Collections.reverse(nodeVersions);
137+
138+
logger.debug("Available node versions: {}", nodeVersions);
139+
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));
140+
this.logger.info("Found matching node version {} satisfying requirement {}.", this.nodeVersion, this.nodeVersionRequirement);
141+
} catch (IOException | DownloadException e) {
142+
throw new InstallationException("Could not get available node versions.", e);
143+
}
144+
}
145+
84146
this.logger.info("Installing node version {}", this.nodeVersion);
85147
if (!this.nodeVersion.startsWith("v")) {
86148
this.logger.warn("Node version does not start with naming convention 'v'.");
@@ -96,6 +158,8 @@ public void install() throws InstallationException {
96158
}
97159
}
98160
}
161+
162+
return nodeVersion;
99163
}
100164

101165
private boolean nodeIsAlreadyInstalled() {
@@ -104,14 +168,19 @@ private boolean nodeIsAlreadyInstalled() {
104168
File nodeFile = executorConfig.getNodePath();
105169
if (nodeFile.exists()) {
106170
final String version =
107-
new NodeExecutor(executorConfig, Arrays.asList("--version"), null).executeAndGetResult(logger);
171+
new NodeExecutor(executorConfig, Arrays.asList("--version"), null).executeAndGetResult(logger);
108172

109-
if (version.equals(this.nodeVersion)) {
173+
if (nodeVersionRequirement != null && nodeVersionRequirement.isSatisfiedBy(new Semver(version, Semver.SemverType.NPM))) {
174+
//update version with installed version
175+
this.nodeVersion = version;
176+
this.logger.info("Node {} matches required version range {} installed.", version, nodeVersionRequirement);
177+
return true;
178+
} else if (version.equals(this.nodeVersion)) {
110179
this.logger.info("Node {} is already installed.", version);
111180
return true;
112181
} else {
113182
this.logger.info("Node {} was installed, but we need version {}", version,
114-
this.nodeVersion);
183+
this.nodeVersion);
115184
return false;
116185
}
117186
} else {

0 commit comments

Comments
 (0)