View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.maven.plugins.nodejs.mojo;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.List;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.maven.artifact.Artifact;
28  import org.apache.maven.artifact.DefaultArtifact;
29  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
30  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
31  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
32  import org.apache.maven.artifact.resolver.ArtifactResolver;
33  import org.apache.maven.artifact.versioning.ComparableVersion;
34  import org.apache.maven.artifact.versioning.VersionRange;
35  import org.apache.maven.execution.MavenSession;
36  import org.apache.maven.model.Dependency;
37  import org.apache.maven.plugin.AbstractMojo;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugins.annotations.Component;
40  import org.apache.maven.plugins.annotations.Parameter;
41  import org.apache.maven.project.MavenProject;
42  import org.codehaus.plexus.util.FileUtils;
43  
44  import io.wcm.maven.plugins.nodejs.installation.NodeInstallationInformation;
45  import io.wcm.maven.plugins.nodejs.installation.NodeUnarchiveTask;
46  
47  /**
48   * Common Node.js Mojo functionality.
49   */
50  public abstract class AbstractNodeJsMojo extends AbstractMojo {
51  
52    /**
53     * Node.js version (minimum version: 6.3.0).
54     */
55    @Parameter(property = "nodejs.version", defaultValue = "10.15.3", required = true)
56    protected String nodeJsVersion;
57  
58    /**
59     * NPM version. If not set the NPM version that is bundled with Node.js is used.
60     */
61    @Parameter(property = "nodejs.npm.version")
62    protected String npmVersion;
63  
64    /**
65     * Default location where Node.js will be extracted to and run from
66     */
67    @Parameter(property = "nodejs.directory", defaultValue = "${java.io.tmpdir}/nodejs")
68    protected File nodeJsDirectory;
69  
70    /**
71     * Tasks that should be run on Node.js execution.
72     * <p>
73     * You can define different types of tasks: <code>npmInstallTask</code> or <code>nodeJsTask</code> items.
74     * </p>
75     * <p>
76     * Example 1:
77     * </p>
78     *
79     * <pre>
80     * &lt;tasks&gt;
81     *   &lt;npmInstallTask&gt;
82     *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
83     *   &lt;/npmInstallTask&gt;
84     *   &lt;nodeJsTask&gt;
85     *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
86     *     &lt;moduleName&gt;grunt-cli&lt;/moduleName&gt;
87     *     &lt;executableName&gt;grunt&lt;/executableName&gt;
88     *     &lt;arguments&gt;
89     *       &lt;argument&gt;build&lt;/argument&gt;
90     *     &lt;/arguments&gt;
91     *   &lt;/nodeJsTask&gt;
92     * &lt;/tasks&gt;
93     * </pre>
94     * <p>
95     * Example 2:
96     * </p>
97     *
98     * <pre>
99     * &lt;tasks&gt;
100    *   &lt;npmInstallTask&gt;
101    *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
102    *   &lt;/npmInstallTask&gt;
103    *   &lt;nodeJsTask&gt;
104    *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
105    *     &lt;moduleName&gt;npm&lt;/moduleName&gt;
106    *     &lt;executableName&gt;npm-cli&lt;/executableName&gt;
107    *     &lt;arguments&gt;
108    *       &lt;argument&gt;run&lt;/argument&gt;
109    *       &lt;argument&gt;test&lt;/argument&gt;
110    *     &lt;/arguments&gt;
111    *   &lt;/nodeJsTask&gt;
112    * &lt;/tasks&gt;
113    * </pre>
114    */
115   @Parameter
116   protected List<? extends Task> tasks;
117 
118   /**
119    * Stop maven build if error occurs.
120    */
121   @Parameter(defaultValue = "true")
122   protected boolean stopOnError;
123 
124   /**
125    * If set to true all Node.js plugin operations are skipped.
126    */
127   @Parameter(property = "nodejs.skip")
128   protected boolean skip;
129 
130   @Parameter(defaultValue = "${project}", readonly = true)
131   private MavenProject project;
132   @Parameter(defaultValue = "${session}", readonly = true)
133   private MavenSession session;
134   @Component
135   private ArtifactHandlerManager artifactHandlerManager;
136   @Component
137   private ArtifactResolver resolver;
138 
139   private static final ComparableVersion NODEJS_MIN_VERSION = new ComparableVersion("6.3.0");
140 
141   /**
142    * Installs node js if necessary and performs defined tasks
143    * @throws MojoExecutionException Mojo execution exception
144    */
145   public void run() throws MojoExecutionException {
146     if (skip) {
147       return;
148     }
149 
150     if (tasks == null || tasks.isEmpty()) {
151       getLog().warn("No Node.js tasks have been defined. Nothing to do.");
152     }
153 
154     // validate nodejs version
155     ComparableVersion nodeJsVersionComparable = new ComparableVersion(nodeJsVersion);
156     if (nodeJsVersionComparable.compareTo(NODEJS_MIN_VERSION) < 0) {
157       throw new MojoExecutionException("This plugin supports Node.js " + NODEJS_MIN_VERSION + " and up.");
158     }
159 
160     NodeInstallationInformation information = getOrInstallNodeJS();
161 
162     if (tasks != null) {
163       for (Task task : tasks) {
164         task.setLog(getLog());
165         task.execute(information);
166       }
167     }
168   }
169 
170   private NodeInstallationInformation getOrInstallNodeJS() throws MojoExecutionException {
171     NodeInstallationInformation information = NodeInstallationInformation.forVersion(nodeJsVersion, npmVersion, nodeJsDirectory);
172     try {
173       if (!information.getNodeExecutable().exists() || !information.getNpmExecutableBundledWithNodeJs().exists()) {
174         getLog().info("Install Node.js to " + information.getNodeJsInstallPath());
175         if (!cleanNodeJsInstallPath(information)) {
176           throw new MojoExecutionException("Could not delete node js directory: " + information.getNodeJsInstallPath());
177         }
178         File nodeJsBinary = resolveArtifact(information.getNodeJsDependency());
179         FileUtils.copyFile(nodeJsBinary, information.getArchive());
180         Task installationTask = new NodeUnarchiveTask(nodeJsDirectory.getAbsolutePath());
181         installationTask.setLog(getLog());
182         installationTask.execute(information);
183       }
184 
185       if (StringUtils.isNotEmpty(npmVersion) && !information.getNpmExecutable().exists()) {
186         updateNPMExecutable(information);
187       }
188     }
189     catch (java.net.MalformedURLException ex) {
190       throw new MojoExecutionException("Malformed provided node URL", ex);
191     }
192     catch (IOException ex) {
193       getLog().error("Failed to get nodeJs from " + information.getNodeJsDependency(), ex);
194       throw new MojoExecutionException("Failed to downloading nodeJs from " + information.getNodeJsDependency(), ex);
195     }
196     catch (MojoExecutionException ex) {
197       getLog().error("Execution Exception", ex);
198       if (stopOnError) {
199         throw new MojoExecutionException("Execution Exception", ex);
200       }
201     }
202     return information;
203   }
204 
205   private boolean cleanNodeJsInstallPath(NodeInstallationInformation information) {
206     File directory = new File(information.getNodeJsInstallPath());
207     if (directory.exists()) {
208       try {
209         FileUtils.deleteDirectory(directory);
210       }
211       catch (IOException ex) {
212         getLog().error(ex);
213         return false;
214       }
215     }
216 
217     if (information.getArchive().exists()) {
218       if (!information.getArchive().delete()) {
219         return false;
220       }
221     }
222 
223     return true;
224   }
225 
226   /**
227    * Makes sure the specified npm version is installed in the base directory, regardless in which environment.
228    * @param information Information
229    */
230   private void updateNPMExecutable(NodeInstallationInformation information) throws MojoExecutionException {
231     getLog().info("Installing specified npm version " + npmVersion);
232     NpmInstallTask npmInstallTask = new NpmInstallTask();
233     npmInstallTask.setLog(getLog());
234     npmInstallTask.setNpmBundledWithNodeJs(true);
235     npmInstallTask.setArguments(new String[] {
236         "--prefix", information.getNpmPrefixPath(), "--global", "npm@" + npmVersion
237     });
238     npmInstallTask.execute(information);
239   }
240 
241   @SuppressWarnings("deprecation")
242   private File resolveArtifact(Dependency dependency) throws MojoExecutionException {
243     Artifact artifact = new DefaultArtifact(dependency.getGroupId(),
244         dependency.getArtifactId(),
245         VersionRange.createFromVersion(dependency.getVersion()),
246         Artifact.SCOPE_PROVIDED,
247         dependency.getType(),
248         dependency.getClassifier(),
249         artifactHandlerManager.getArtifactHandler(dependency.getType()));
250     try {
251       resolver.resolve(artifact, project.getRemoteArtifactRepositories(), session.getLocalRepository());
252     }
253     catch (ArtifactResolutionException ex) {
254       throw new MojoExecutionException("Unable to get artifact for " + dependency, ex);
255     }
256     catch (ArtifactNotFoundException ex) {
257       throw new MojoExecutionException("Unable to get artifact for " + dependency, ex);
258     }
259     return artifact.getFile();
260   }
261 
262 }