Skip to content

Commit

Permalink
feat: allow specifying license requirement and warn on license violat…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
mjeanroy committed Aug 12, 2019
1 parent 1014a2f commit be3ee04
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 49 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"lodash": "4.17.15",
"magic-string": "0.25.3",
"mkdirp": "0.5.1",
"moment": "2.24.0"
"moment": "2.24.0",
"spdx-expression-validate": "2.0.0",
"spdx-satisfies": "5.0.0"
},
"devDependencies": {
"@babel/core": "7.5.5",
Expand Down
2 changes: 1 addition & 1 deletion src/index-rollup-legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ module.exports = (options = {}) => {
* @return {void}
*/
ongenerate() {
plugin.exportThirdParties();
plugin.scanThirdParties();
},
};
};
2 changes: 1 addition & 1 deletion src/index-rollup-stable.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ module.exports = (options = {}) => {
* @return {void}
*/
generateBundle() {
plugin.exportThirdParties();
plugin.scanThirdParties();
},
};
};
1 change: 1 addition & 0 deletions src/license-plugin-option.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const SCHEMA = {
Joi.func(),
Joi.object().keys({
includePrivate: Joi.boolean(),
allow: Joi.string(),
output: [
Joi.func(),
Joi.string(),
Expand Down
124 changes: 93 additions & 31 deletions src/license-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const _ = require('lodash');
const moment = require('moment');
const MagicString = require('magic-string');
const glob = require('glob');
const spdxExpressionValidate = require('spdx-expression-validate');
const spdxSatisfies = require('spdx-satisfies');

const Dependency = require('./dependency.js');
const generateBlockComment = require('./generate-block-comment.js');
const licensePluginOptions = require('./license-plugin-option.js');
Expand Down Expand Up @@ -259,12 +262,14 @@ class LicensePlugin {
}

/**
* Generate third-party dependencies summary.
* Scan third-party dependencies, and:
* - Warn for license violations.
* - Generate summary.
*
* @param {boolean} includePrivate Flag that can be used to include / exclude private dependencies.
* @return {void}
*/
exportThirdParties() {
scanThirdParties() {
const thirdParty = this._options.thirdParty;
if (!thirdParty) {
return;
Expand All @@ -280,37 +285,15 @@ class LicensePlugin {
return thirdParty(outputDependencies);
}

const output = thirdParty.output;
if (!output) {
return;
const allow = thirdParty.allow;
if (allow) {
this._scanLicenseViolations(outputDependencies, allow);
}

if (_.isFunction(output)) {
return output(outputDependencies);
const output = thirdParty.output;
if (output) {
return this._exportThirdParties(outputDependencies, output);
}

// Default is to export to given file.

// Allow custom formatting of output using given template option.
const template = _.isString(output.template) ? (dependencies) => _.template(output.template)({dependencies, _, moment}) : output.template;
const defaultTemplate = (dependencies) => (
_.isEmpty(dependencies) ? 'No third parties dependencies' : _.map(dependencies, (d) => d.text()).join(`${EOL}${EOL}---${EOL}${EOL}`)
);

const text = _.isFunction(template) ? template(outputDependencies) : defaultTemplate(outputDependencies);
const isOutputFile = _.isString(output);
const file = isOutputFile ? output : output.file;
const encoding = isOutputFile ? 'utf-8' : (output.encoding || 'utf-8');

this.debug(`exporting third-party summary to ${file}`);
this.debug(`use encoding: ${encoding}`);

// Create directory if it does not already exist.
mkdirp.sync(path.parse(file).dir);

fs.writeFileSync(file, (text || '').trim(), {
encoding,
});
}

/**
Expand All @@ -321,10 +304,20 @@ class LicensePlugin {
*/
debug(msg) {
if (this._debug) {
console.log(`[${this.name}] -- ${msg}`);
console.debug(`[${this.name}] -- ${msg}`);
}
}

/**
* Log warn message.
*
* @param {string} msg Log message.
* @return {void}
*/
warn(msg) {
console.warn(`[${this.name}] -- ${msg}`);
}

/**
* Read banner from given options and returns it.
*
Expand Down Expand Up @@ -407,6 +400,75 @@ class LicensePlugin {

return COMMENT_STYLES[style] ? generateBlockComment(text, COMMENT_STYLES[style]) : text;
}

/**
* Scan for dependency violations and print a warning if some violations are found.
*
* @param {Array<Object>} outputDependencies The dependencies to scan.
* @param {string} allow The allowed licenses as a SPDX pattern.
* @return {void}
*/
_scanLicenseViolations(outputDependencies, allow) {
_.forEach(outputDependencies, (dependency) => {
this._scanLicenseViolation(dependency, allow);
});
}

/**
* Scan dependency for a dependency violation.
*
* @param {Object} dependency The dependency to scan.
* @param {string} allow The allowed licenses as a SPDX pattern.
* @return {void}
*/
_scanLicenseViolation(dependency, allow) {
const license = dependency.license || 'UNLICENSED';
if (license === 'UNLICENSED') {
this.warn(`Dependency "${dependency.name}" does not specify any license.`);
} else if (!spdxExpressionValidate(license) || !spdxSatisfies(license, allow)) {
this.warn(
`Dependency "${dependency.name}" has a license (${dependency.license}) which is not compatible with requirement (${allow}), ` +
`looks like a license violation to fix.`
);
}
}

/**
* Export scanned third party dependencies to a destination output (a function, a
* file written to disk, etc.).
*
* @param {Array<Object>} outputDependencies The dependencies to include in the output.
* @param {Object|function|string} output The output destination.
* @return {void}
*/
_exportThirdParties(outputDependencies, output) {
if (_.isFunction(output)) {
return output(outputDependencies);
}

// Default is to export to given file.

// Allow custom formatting of output using given template option.
const template = _.isString(output.template) ? (dependencies) => _.template(output.template)({dependencies, _, moment}) : output.template;
const defaultTemplate = (dependencies) => (
_.isEmpty(dependencies) ? 'No third parties dependencies' : _.map(dependencies, (d) => d.text()).join(`${EOL}${EOL}---${EOL}${EOL}`)
);

const text = _.isFunction(template) ? template(outputDependencies) : defaultTemplate(outputDependencies);
const isOutputFile = _.isString(output);
const file = isOutputFile ? output : output.file;
const encoding = isOutputFile ? 'utf-8' : (output.encoding || 'utf-8');

this.debug(`exporting third-party summary to ${file}`);
this.debug(`use encoding: ${encoding}`);

// Create directory if it does not already exist.
mkdirp.sync(path.parse(file).dir);

fs.writeFileSync(file, (text || '').trim(), {
encoding,
});
}
}

/**
Expand Down
Loading

0 comments on commit be3ee04

Please sign in to comment.