AbstractNodeJsMojo.java

  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. import java.io.File;
  22. import java.io.IOException;
  23. import java.util.List;

  24. import org.apache.commons.lang3.StringUtils;
  25. import org.apache.maven.artifact.Artifact;
  26. import org.apache.maven.artifact.DefaultArtifact;
  27. import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
  28. import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
  29. import org.apache.maven.artifact.resolver.ArtifactResolutionException;
  30. import org.apache.maven.artifact.resolver.ArtifactResolver;
  31. import org.apache.maven.artifact.versioning.ComparableVersion;
  32. import org.apache.maven.artifact.versioning.VersionRange;
  33. import org.apache.maven.execution.MavenSession;
  34. import org.apache.maven.model.Dependency;
  35. import org.apache.maven.plugin.AbstractMojo;
  36. import org.apache.maven.plugin.MojoExecutionException;
  37. import org.apache.maven.plugins.annotations.Component;
  38. import org.apache.maven.plugins.annotations.Parameter;
  39. import org.apache.maven.project.MavenProject;
  40. import org.codehaus.plexus.util.FileUtils;

  41. import io.wcm.maven.plugins.nodejs.installation.NodeInstallationInformation;
  42. import io.wcm.maven.plugins.nodejs.installation.NodeUnarchiveTask;

  43. /**
  44.  * Common Node.js Mojo functionality.
  45.  */
  46. public abstract class AbstractNodeJsMojo extends AbstractMojo {

  47.   /**
  48.    * Node.js version (minimum version: 6.3.0). Can be specified with or without "v" prefix.
  49.    */
  50.   @Parameter(property = "nodejs.version", defaultValue = "10.15.3", required = true)
  51.   protected String nodeJsVersion;

  52.   /**
  53.    * NPM version. If not set the NPM version that is bundled with Node.js is used.
  54.    */
  55.   @Parameter(property = "nodejs.npm.version")
  56.   protected String npmVersion;

  57.   /**
  58.    * Default location where Node.js will be extracted to and run from
  59.    */
  60.   @Parameter(property = "nodejs.directory", defaultValue = "${java.io.tmpdir}/nodejs")
  61.   protected File nodeJsDirectory;

  62.   /**
  63.    * Tasks that should be run on Node.js execution.
  64.    * <p>
  65.    * You can define different types of tasks: <code>npmInstallTask</code> or <code>nodeJsTask</code> items.
  66.    * </p>
  67.    * <p>
  68.    * Example 1:
  69.    * </p>
  70.    *
  71.    * <pre>
  72.    * &lt;tasks&gt;
  73.    *   &lt;npmInstallTask&gt;
  74.    *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
  75.    *   &lt;/npmInstallTask&gt;
  76.    *   &lt;nodeJsTask&gt;
  77.    *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
  78.    *     &lt;moduleName&gt;grunt-cli&lt;/moduleName&gt;
  79.    *     &lt;executableName&gt;grunt&lt;/executableName&gt;
  80.    *     &lt;arguments&gt;
  81.    *       &lt;argument&gt;build&lt;/argument&gt;
  82.    *     &lt;/arguments&gt;
  83.    *   &lt;/nodeJsTask&gt;
  84.    * &lt;/tasks&gt;
  85.    * </pre>
  86.    * <p>
  87.    * Example 2:
  88.    * </p>
  89.    *
  90.    * <pre>
  91.    * &lt;tasks&gt;
  92.    *   &lt;npmInstallTask&gt;
  93.    *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
  94.    *   &lt;/npmInstallTask&gt;
  95.    *   &lt;nodeJsTask&gt;
  96.    *     &lt;workingDirectory&gt;${frontend.dir}&lt;/workingDirectory&gt;
  97.    *     &lt;moduleName&gt;npm&lt;/moduleName&gt;
  98.    *     &lt;executableName&gt;npm-cli&lt;/executableName&gt;
  99.    *     &lt;arguments&gt;
  100.    *       &lt;argument&gt;run&lt;/argument&gt;
  101.    *       &lt;argument&gt;test&lt;/argument&gt;
  102.    *     &lt;/arguments&gt;
  103.    *   &lt;/nodeJsTask&gt;
  104.    * &lt;/tasks&gt;
  105.    * </pre>
  106.    */
  107.   @Parameter
  108.   protected List<? extends Task> tasks;

  109.   /**
  110.    * Stop maven build if error occurs.
  111.    */
  112.   @Parameter(defaultValue = "true")
  113.   protected boolean stopOnError;

  114.   /**
  115.    * If set to true all Node.js plugin operations are skipped.
  116.    */
  117.   @Parameter(property = "nodejs.skip")
  118.   protected boolean skip;

  119.   @Parameter(defaultValue = "${project}", readonly = true)
  120.   private MavenProject project;
  121.   @Parameter(defaultValue = "${session}", readonly = true)
  122.   private MavenSession session;
  123.   @Component
  124.   private ArtifactHandlerManager artifactHandlerManager;
  125.   @Component
  126.   private ArtifactResolver resolver;

  127.   private static final ComparableVersion NODEJS_MIN_VERSION = new ComparableVersion("6.3.0");

  128.   /**
  129.    * Installs node js if necessary and performs defined tasks
  130.    * @throws MojoExecutionException Mojo execution exception
  131.    */
  132.   public void run() throws MojoExecutionException {
  133.     if (skip) {
  134.       return;
  135.     }

  136.     if (tasks == null || tasks.isEmpty()) {
  137.       getLog().warn("No Node.js tasks have been defined. Nothing to do.");
  138.     }

  139.     // validate nodejs version
  140.     ComparableVersion nodeJsVersionComparable = new ComparableVersion(cleanupVersion(nodeJsVersion));
  141.     if (nodeJsVersionComparable.compareTo(NODEJS_MIN_VERSION) < 0) {
  142.       throw new MojoExecutionException("This plugin supports Node.js " + NODEJS_MIN_VERSION + " and up.");
  143.     }

  144.     NodeInstallationInformation information = getOrInstallNodeJS();

  145.     if (tasks != null) {
  146.       for (Task task : tasks) {
  147.         task.setLog(getLog());
  148.         task.execute(information);
  149.       }
  150.     }
  151.   }

  152.   private NodeInstallationInformation getOrInstallNodeJS() throws MojoExecutionException {
  153.     NodeInstallationInformation information = NodeInstallationInformation.forVersion(cleanupVersion(nodeJsVersion), npmVersion, nodeJsDirectory);
  154.     try {
  155.       if (!information.getNodeExecutable().exists() || !information.getNpmExecutableBundledWithNodeJs().exists()) {
  156.         getLog().info("Install Node.js to " + information.getNodeJsInstallPath());
  157.         if (!cleanNodeJsInstallPath(information)) {
  158.           throw new MojoExecutionException("Could not delete node js directory: " + information.getNodeJsInstallPath());
  159.         }
  160.         File nodeJsBinary = resolveArtifact(information.getNodeJsDependency());
  161.         FileUtils.copyFile(nodeJsBinary, information.getArchive());
  162.         Task installationTask = new NodeUnarchiveTask(nodeJsDirectory.getAbsolutePath());
  163.         installationTask.setLog(getLog());
  164.         installationTask.execute(information);
  165.       }

  166.       if (StringUtils.isNotEmpty(npmVersion) && !information.getNpmExecutable().exists()) {
  167.         updateNPMExecutable(information);
  168.       }
  169.     }
  170.     catch (java.net.MalformedURLException ex) {
  171.       throw new MojoExecutionException("Malformed provided node URL", ex);
  172.     }
  173.     catch (IOException ex) {
  174.       getLog().error("Failed to get nodeJs from " + information.getNodeJsDependency(), ex);
  175.       throw new MojoExecutionException("Failed to downloading nodeJs from " + information.getNodeJsDependency(), ex);
  176.     }
  177.     catch (MojoExecutionException ex) {
  178.       getLog().error("Execution Exception", ex);
  179.       if (stopOnError) {
  180.         throw new MojoExecutionException("Execution Exception", ex);
  181.       }
  182.     }
  183.     return information;
  184.   }

  185.   private boolean cleanNodeJsInstallPath(NodeInstallationInformation information) {
  186.     File directory = new File(information.getNodeJsInstallPath());
  187.     if (directory.exists()) {
  188.       try {
  189.         FileUtils.deleteDirectory(directory);
  190.       }
  191.       catch (IOException ex) {
  192.         getLog().error(ex);
  193.         return false;
  194.       }
  195.     }

  196.     if (information.getArchive().exists()) {
  197.       if (!information.getArchive().delete()) {
  198.         return false;
  199.       }
  200.     }

  201.     return true;
  202.   }

  203.   /**
  204.    * Makes sure the specified npm version is installed in the base directory, regardless in which environment.
  205.    * @param information Information
  206.    */
  207.   private void updateNPMExecutable(NodeInstallationInformation information) throws MojoExecutionException {
  208.     getLog().info("Installing specified npm version " + npmVersion);
  209.     NpmInstallTask npmInstallTask = new NpmInstallTask();
  210.     npmInstallTask.setLog(getLog());
  211.     npmInstallTask.setNpmBundledWithNodeJs(true);
  212.     npmInstallTask.setArguments(new String[] {
  213.         "--prefix", information.getNpmPrefixPath(), "--global", "npm@" + npmVersion
  214.     });
  215.     npmInstallTask.execute(information);
  216.   }

  217.   @SuppressWarnings("deprecation")
  218.   private File resolveArtifact(Dependency dependency) throws MojoExecutionException {
  219.     Artifact artifact = new DefaultArtifact(dependency.getGroupId(),
  220.         dependency.getArtifactId(),
  221.         VersionRange.createFromVersion(dependency.getVersion()),
  222.         Artifact.SCOPE_PROVIDED,
  223.         dependency.getType(),
  224.         dependency.getClassifier(),
  225.         artifactHandlerManager.getArtifactHandler(dependency.getType()));
  226.     try {
  227.       resolver.resolve(artifact, project.getRemoteArtifactRepositories(), session.getLocalRepository());
  228.     }
  229.     catch (ArtifactResolutionException ex) {
  230.       throw new MojoExecutionException("Unable to get artifact for " + dependency, ex);
  231.     }
  232.     catch (ArtifactNotFoundException ex) {
  233.       throw new MojoExecutionException("Unable to get artifact for " + dependency, ex);
  234.     }
  235.     return artifact.getFile();
  236.   }

  237.   /**
  238.    * Removes "v" prefix from version, if present.
  239.    * @param version Version number
  240.    * @return Version number
  241.    */
  242.   private static String cleanupVersion(String version) {
  243.     return StringUtils.removeStart(version, "v");
  244.   }

  245. }