license-checker
license-checker copied to clipboard
highcharts license incorrectly reported using highcharts-server license URL
package.json file:
{
"dependencies": {
"react-highcharts": "^16.1.0"
}
}
Run
npm i
license-checker --packages "[email protected]"
Output:
└─ [email protected]
├─ licenses: Custom: https://www.npmjs.com/package/highcharts-server
...
However, node_modules/highcharts/package.json has this:
"license": "https://www.highcharts.com/license"
The difference is significant, because highcharts-server is MIT-licensed, while highcharts has a commercial license.
I've been investigating this, and found some interesting tidbits:
-
license-checker
usesread-installed
to traverse the tree of installed packages.read-installed
returns a data structure that has a few properties, one of which isreadme
, another islicense
. - If there is no
readme
,license-checker
will read theREADME.md
file of the package and put that string in thereadme
property of the data returned fromread-installed
. - If there is no
license
orlicenses
property in the data returned fromread-installed
,license-checker
will use thereadme
property and look for licenses in there instead, using a bunch of regexes, one of which is an URL regex. - The
README.md
file for[email protected]
contains a link to https://www.npmjs.com/package/highcharts-server early on. - My guess is that for some reason,
read-installed
does not properly return thelicense
property of this package, causinglicense-checker
to grab the incorrect URL from theREADME.md
instead. - Remaining question is why
read-installed
does not return thelicense
property of[email protected]
, even though it clearly has such a property in itspackage.json
Not sure if anyone will ever look into this discussion again, but I just stumbled across this issue in combination with a custom license that was used in one of my packages.
I think I was able to track it down to this portion of code from index.js of license-checker (Lines 151 to 172 in Version: 25.0.1)
if (json.path && fs.existsSync(json.path)) {
dirFiles = fs.readdirSync(json.path);
files = licenseFiles(dirFiles);
noticeFiles = dirFiles.filter(function(filename) {
filename = filename.toUpperCase();
var name = path.basename(filename).replace(path.extname(filename), '');
return name === 'NOTICE';
});
}
files.forEach(function(filename, index) {
licenseFile = path.join(json.path, filename);
// Checking that the file is in fact a normal file and not a directory for example.
/*istanbul ignore else*/
if (fs.lstatSync(licenseFile).isFile()) {
var content;
if (!moduleInfo.licenses || moduleInfo.licenses.indexOf(UNKNOWN) > -1 || moduleInfo.licenses.indexOf('Custom:') === 0) {
//Only re-check the license if we didn't get it from elsewhere
content = fs.readFileSync(licenseFile, { encoding: 'utf8' });
moduleInfo.licenses = license(content);
}
The "files" array is populated with all the files that are assumed to be related to a license by the licenseFiles
function - basically checking if the file is named LICENSE, LICENSE.MD, LICENCE, LICENCE.MD or ... README[.MD]
Now if the currently found moduleInfo.licenses starts with "Custom:", which is the case if the the license-field of the package.json does not carry a valid spdx-Expression, it will replace that moduleInfo.licenses with something found in one of the files above.
If the package happens to contain a readme.md file and that readme.md file happens to contains a URL, the license
function would actually match that URL with the IS_URL
-Regex and return "Custom: <FOUND_URL>" here:
match = IS_URL.exec(str) || IS_FILE_REFERENCE.exec(str);
if(match) {
return 'Custom: ' + match[1];
} else {
return null;
}