onebeyond/license-checker

View on GitHub
src/utils.js

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
const isSPDXCompliant = require('spdx-expression-validate');
const satisfiesSPDXLicense = require('spdx-satisfies');
const spdxIds = require('spdx-license-ids');

// @TODO Remove after issue has been solved
const licensesExceptions = ['GFDL-1.1-no-invariants-or-later', 'GFDL-1.1-invariants-or-later', 'GFDL-1.2-invariants-or-later', 'GFDL-1.2-no-invariants-or-later', 'GFDL-1.3-invariants-or-later', 'GFDL-1.3-no-invariants-or-later'];

/**
 * Generate objects with information on each package that we want to include
 * in the report files. This will include metadata such as the package name,
 * author or license name, among others.
 *
 *  The fields `path` and `licenseFile` will be excluded from the output.
 *
 * @param {object} packages - Map of packages installed in the project where
 *  the script is run
 *
 * @returns List of objects with package metadata
 */
const getPackageInfoList = packages => Object.entries(packages)
  .map(([key, value]) => {
    const { path, licenseFile, ...rest } = value;
    const validInfo = {
      package: key,
      ...rest
    };

    return validInfo;
  });

/**
 * Format the forbidden licenses identified in a multiline string
 *
 * @param {object[]} licenses - List of licenses identified
 * @returns {string} - A string with the number and list of forbidden licenses identified
 */
const formatForbiddenLicenseError = licenses => {
  const forbiddenLicenseStats = licenses
    .reduce((stats, { licenses }) => ({
      ...stats,
      [licenses]: !stats[licenses] ? 1 : stats[licenses] + 1
    }), {});

  const header = `Found ${licenses.length} packages with licenses defined by the --failOn flag:`;
  const lines = Object
    .entries(forbiddenLicenseStats)
    .map(([license, value]) => ` > ${value} packages with license ${license}`)
    .join('\n');

  return `${header}\n${lines}`;
};

/**
 * Generates a SPDX expression joining the input licenses with the OR operator
 * @param {string[]} licenses - List of SPDX licenses
 * @return {string} - A string with the input licenses joined with the OR operator.
 * e.g. input -> ['MIT', 'GPL-1.0'] output -> 'MIT OR GPL-1.0'
 */
const generateSPDXExpression = (licenses = []) => licenses.join(' OR ');

/**
 * Checks whether the input licenses are SPDX compliant. Throws an error with the invalid licenses
 * @param licenses - List of licenses
 */
const checkSPDXCompliance = (licenses = []) => {
  const invalidLicenses = licenses.filter(arg => !isSPDXCompliant(arg));
  if (invalidLicenses.length) {
    throw new Error(
      `The following licenses are not SPDX compliant. Please, use the --checkLicense option to validate your input:\n${invalidLicenses.join(' | ')}`
    );
  }
};

// @TODO Remove after issue has been solved
/**
 * Throws an error inf any the input licenses is a license affected by the spdx-satisfies issue.
 * @param licenses - List of SPDX licenses
 */
const checkLicenseError = (licenses = []) => {
  const errorLicenses = licenses.some(isLicenseError);
  if (errorLicenses) {
    throw new Error(
      'Your failOn list contains a GFDL-1.x licenses and they are temporary unallowed. There\'s an issue pending to solve.'
    );
  }
};

// @TODO Remove after issue has been solved
/**
 * This is a temporal fix until an issue in spdx-satisfies package has been solved. The package itself throws an error if a GFDL license is used in the check: https://github.com/jslicense/spdx-satisfies.js/issues/15
 * @param {string} license - A SPDX license
 * @return {boolean}
 */
const isLicenseError = (license = '') => licensesExceptions.includes(license);

/**
 * Subtracts the expression from the full list of SPDX ids and check the result (the allowed licenses) against the list of packages.
 * If the license of the package itself is not SPDX compliant, the package will be included on the "nonCompliant" list.
 * If a package license does not satisfy the allowed SPDX id list, the package will be included on the "forbidden" list.
 * @param {string} expression - A SPDX expression
 * @param {object[]} packages - A list of packages to be checked against the SPDX expression
 * @return {{forbidden: object[], nonCompliant: object[]}} - A couple of lists including the packages that satisfy the SPDX expression
 * and the packages with a non SPDX compliant license
 */
const checkPackagesLicenses = (expression, packages) => {
  const validSpdxIds = expression && spdxIds.filter(id => !isLicenseError(id) && !satisfiesSPDXLicense(id, expression)); // @TODO Refactor after issue has been solved
  const allowedLicensesExp = expression && generateSPDXExpression(validSpdxIds);

  return packages.reduce((total, pkg) => {
    const { licenses } = pkg;

    if (!isSPDXCompliant(licenses)) return { ...total, nonCompliant: [...total.nonCompliant, pkg] };

    const isSatisfiedLicense = expression && !satisfiesSPDXLicense(licenses, allowedLicensesExp);
    if (isSatisfiedLicense) return { ...total, forbidden: [...total.forbidden, pkg] };

    return total;
  }, { forbidden: [], nonCompliant: [] });
};

module.exports = {
  getPackageInfoList,
  formatForbiddenLicenseError,
  generateSPDXExpression,
  checkSPDXCompliance,
  checkPackagesLicenses,
  isLicenseError,
  checkLicenseError
};