NodeInstallationInformation.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 wcm.io
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package io.wcm.maven.plugins.nodejs.installation;

import java.io.File;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.Os;

/**
 * Holds the general information about the node installation. Provides node and npm executables
 */
public class NodeInstallationInformation {

  private static final String NODEJS_BINARIES_GROUPID = "org.nodejs.dist";
  private static final String NODEJS_BINARIES_ARTIFACTID = "nodejs-binaries";
  private static final String NPM_CLI_EXECUTABLE_PATH = File.separator + "npm" + File.separator + "bin" + File.separator + "npm-cli.js";

  private static final String TYPE_TAR_GZ = "tar.gz";
  static final String TYPE_ZIP = "zip";

  private static final String OS_WINDOWS = "win";
  private static final String OS_MACOS = "darwin";
  private static final String OS_LINUX = "linux";

  private Dependency nodeJsDependency;
  private Dependency npmDependency;
  private File archive;
  private String nodeExecutableRelativePath;
  private String nodeJsInstallPath;
  private String npmPrefixPath;
  private String nodeModulesBuiltInRootPath;
  private String nodeModulesRootPath;

  public Dependency getNodeJsDependency() {
    return this.nodeJsDependency;
  }

  void setNodeJsDependency(Dependency nodeJsDependency) {
    this.nodeJsDependency = nodeJsDependency;
  }

  public Dependency getNpmDependency() {
    return this.npmDependency;
  }

  void setNpmDependency(Dependency npmDependency) {
    this.npmDependency = npmDependency;
  }

  public File getArchive() {
    return archive;
  }

  void setArchive(File archive) {
    this.archive = archive;
  }

  public File getNodeExecutable() {
    return new File(this.nodeJsInstallPath + File.separator + nodeExecutableRelativePath);
  }

  void setNodeExecutableRelativePath(String nodeExecutableRelativePath) {
    this.nodeExecutableRelativePath = nodeExecutableRelativePath;
  }

  public File getNpmExecutable() {
    return new File(this.nodeModulesRootPath + File.separator + "node_modules" + NPM_CLI_EXECUTABLE_PATH);
  }

  public File getNpmExecutableBundledWithNodeJs() {
    return new File(this.nodeModulesBuiltInRootPath + File.separator + "node_modules" + NPM_CLI_EXECUTABLE_PATH);
  }

  public String getNodeJsInstallPath() {
    return nodeJsInstallPath;
  }

  void setNodeJsInstallPath(String nodeJsInstallPath) {
    this.nodeJsInstallPath = nodeJsInstallPath;
  }

  public String getNpmPrefixPath() {
    return this.npmPrefixPath;
  }

  public void setNpmPrefixPath(String npmPrefixPath) {
    this.npmPrefixPath = npmPrefixPath;
  }

  public String getNodeModulesBuiltInRootPath() {
    return this.nodeModulesBuiltInRootPath;
  }

  void setNodeModulesBuiltInRootPath(String nodeModulesBuiltInRootPath) {
    this.nodeModulesBuiltInRootPath = nodeModulesBuiltInRootPath;
  }

  public String getNodeModulesRootPath() {
    return this.nodeModulesRootPath;
  }

  void setNodeModulesRootPath(String nodeModulesRootPath) {
    this.nodeModulesRootPath = nodeModulesRootPath;
  }

  /**
   * Creates a {@link NodeInstallationInformation} for a specific Node.js and npm version and directory
   * @param version Version
   * @param npmVersion NPM version
   * @param directory directory
   * @return {@link NodeInstallationInformation}
   * @throws MojoExecutionException Mojo execution exception
   */
  public static NodeInstallationInformation forVersion(String version, String npmVersion, File directory) throws MojoExecutionException {
    int nodejsMajorVersion = getMajorVersion(version);

    String arch;
    if (Os.isArch("x86") || Os.isArch("i386")) {
      arch = "x86";
    }
    else if (Os.isArch("x86_64") || Os.isArch("amd64")) {
      arch = "x64";
    }
    else if (Os.isArch("aarch64")) {
      if (Os.isFamily(Os.FAMILY_MAC) && nodejsMajorVersion < 16) {
        arch = "x64";
      }
      else {
        arch = "arm64";
      }
    }
    else {
      throw new MojoExecutionException("Unsupported OS arch: " + Os.OS_ARCH);
    }

    NodeInstallationInformation result = new NodeInstallationInformation();

    String basePath = directory.getAbsolutePath() + File.separator;

    if (Os.isFamily(Os.FAMILY_WINDOWS) || Os.isFamily(Os.FAMILY_WIN9X)) {
      String nodeJsInstallPath = basePath + "node-v" + version + "-" + OS_WINDOWS + "-" + arch;
      result.setNodeJsInstallPath(nodeJsInstallPath);
      result.setNodeJsDependency(buildDependency(NODEJS_BINARIES_GROUPID, NODEJS_BINARIES_ARTIFACTID, version, OS_WINDOWS, arch, TYPE_ZIP));
      result.setArchive(new File(nodeJsInstallPath + "." + TYPE_ZIP));
      result.setNodeExecutableRelativePath("node.exe");
      result.setNodeModulesBuiltInRootPath(nodeJsInstallPath);
      result.setNpmPrefixPath(nodeJsInstallPath + getNodeModulesRootPathNpmSuffix(npmVersion));
      result.setNodeModulesRootPath(result.getNpmPrefixPath());
    }
    else if (Os.isFamily(Os.FAMILY_MAC)) {
      String nodeJsInstallPath = basePath + "node-v" + version + "-" + OS_MACOS + "-" + arch;
      result.setNodeJsInstallPath(nodeJsInstallPath);
      result.setNodeJsDependency(buildDependency(NODEJS_BINARIES_GROUPID, NODEJS_BINARIES_ARTIFACTID, version, OS_MACOS, arch, TYPE_TAR_GZ));
      result.setArchive(new File(nodeJsInstallPath + "." + TYPE_TAR_GZ));
      result.setNodeExecutableRelativePath("bin" + File.separator + "node");
      result.setNodeModulesBuiltInRootPath(nodeJsInstallPath + File.separator + "lib");
      result.setNpmPrefixPath(nodeJsInstallPath + getNodeModulesRootPathNpmSuffix(npmVersion));
      result.setNodeModulesRootPath(result.getNpmPrefixPath() + File.separator + "lib");
    }
    else if (Os.isFamily(Os.FAMILY_UNIX)) {
      String nodeJsInstallPath = basePath + "node-v" + version + "-" + OS_LINUX + "-" + arch;
      result.setNodeJsInstallPath(nodeJsInstallPath);
      result.setNodeJsDependency(buildDependency(NODEJS_BINARIES_GROUPID, NODEJS_BINARIES_ARTIFACTID, version, OS_LINUX, arch, TYPE_TAR_GZ));
      result.setArchive(new File(nodeJsInstallPath + "." + TYPE_TAR_GZ));
      result.setNodeExecutableRelativePath("bin" + File.separator + "node");
      result.setNodeModulesBuiltInRootPath(nodeJsInstallPath + File.separator + "lib");
      result.setNpmPrefixPath(nodeJsInstallPath + getNodeModulesRootPathNpmSuffix(npmVersion));
      result.setNodeModulesRootPath(result.getNpmPrefixPath() + File.separator + "lib");
    }
    else {
      throw new MojoExecutionException("Unsupported OS: " + Os.OS_FAMILY);
    }

    return result;
  }

  private static int getMajorVersion(String version) {
    ArtifactVersion versionInfo = new DefaultArtifactVersion(version);
    return versionInfo.getMajorVersion();
  }

  private static String getNodeModulesRootPathNpmSuffix(String npmVersion) {
    if (StringUtils.isNotEmpty(npmVersion)) {
      return File.separator + "npm-v" + npmVersion;
    }
    else {
      return "";
    }
  }

  @SuppressWarnings("PMD.UseStringBufferForStringAppends")
  private static Dependency buildDependency(String groupId, String artifactId, String version, String os, String arch, String type) {
    String classifier = null;
    if (StringUtils.isNotEmpty(os)) {
      classifier = os;
    }
    if (StringUtils.isNotEmpty(arch)) {
      classifier += "-" + arch;
    }

    Dependency dependency = new Dependency();
    dependency.setGroupId(groupId);
    dependency.setArtifactId(artifactId);
    dependency.setVersion(version);
    dependency.setType(type);
    dependency.setClassifier(classifier);
    return dependency;
  }

}