diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96110a9..70cd71e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,5 +9,5 @@ jobs: tests: uses: ljharb/actions/.github/workflows/node-majors.yml@main with: - range: '>= 14.17' + range: '^18.12 || ^20.9 || >= 22.7' command: 'npm run tests-only && npm run licenses' diff --git a/.gitignore b/.gitignore index 32c2eda..443ec43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ **/.package-lock.json -/.nyc-output +/.nyc_output /node_modules /package-lock.json diff --git a/.licensee.json b/.licensee.json index 57d6469..fa810d4 100644 --- a/.licensee.json +++ b/.licensee.json @@ -12,6 +12,7 @@ "doctrine": "1.5.0", "esutils": "2.0.2", "json-schema": "0.2.3", + "jsonp": "0.2.1", "wordwrap": "0.0.2", "longest": "1.0.1", "repeat-element": "1.1.2" diff --git a/index.js b/index.js index eb12f13..d90f153 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ module.exports = licensee var Arborist = require('@npmcli/arborist') var blueOakList = require('@blueoak/list') var correctLicenseMetadata = require('correct-license-metadata') -var has = require('has') +var hasOwn = require('hasown') var npmLicenseCorrections = require('npm-license-corrections') var osi = require('spdx-osi') var parse = require('spdx-expression-parse') @@ -56,7 +56,13 @@ function validConfiguration (configuration) { isObject(configuration) && has(configuration, 'licenses') && isObject(configuration.licenses) && - has(configuration, 'packages') + (!has(configuration.licenses, 'blueOak') || + ( + blueOakList.some(({ name }) => + name.toLowerCase() === configuration.licenses.blueOak.toLowerCase() + ) + )) && + (has(configuration, 'packages') ? ( // Validate `packages` property. isObject(configuration.packages) && @@ -64,7 +70,7 @@ function validConfiguration (configuration) { .every(function (key) { return isString(configuration.packages[key]) }) - ) : true + ) : true) ) } @@ -280,3 +286,7 @@ function pushMissing (source, sink) { if (sink.indexOf(element) === -1) sink.push(element) }) } + +function has (object, key) { + return hasOwn(object, key) && object[key] !== undefined +} diff --git a/licensee b/licensee index a983860..b842558 100755 --- a/licensee +++ b/licensee @@ -1,8 +1,7 @@ #!/usr/bin/env node -var access = require('fs-access') var docopt = require('docopt') var fs = require('fs') -var has = require('has') +var has = require('hasown') var path = require('path') var validSPDX = require('spdx-expression-validate') @@ -87,7 +86,7 @@ if (options['--init']) { } checkDependencies() } else { - access(configurationPath, function (error) { + fs.access(configurationPath, fs.constants.R_OK, function (error) { if (error) { die( [ diff --git a/package.json b/package.json index 2b32614..7c39f4e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "licensee", "description": "check dependency licenses against rules", - "version": "9.0.0", + "version": "11.0.0", "author": "Kyle E. Mitchell (https://kemitchell.com/)", "contributors": [ "Jakob Krigovsky ", @@ -9,15 +9,14 @@ "Andrew Monks " ], "dependencies": { - "@blueoak/list": "^9.0.0", - "@npmcli/arborist": "^6.1.2", + "@blueoak/list": "^15.0.0", + "@npmcli/arborist": "^6.5.0", "correct-license-metadata": "^1.4.0", "docopt": "^0.6.2", - "fs-access": "^2.0.0", - "has": "^1.0.3", + "hasown": "^2.0.0", "npm-license-corrections": "^1.6.2", - "semver": "^7.3.8", - "spdx-expression-parse": "^3.0.1", + "semver": "^7.6.0", + "spdx-expression-parse": "^4.0.0", "spdx-expression-validate": "^2.0.0", "spdx-osi": "^3.0.0", "spdx-whitelisted": "^1.0.0" @@ -29,24 +28,26 @@ "licensee" ], "devDependencies": { - "aud": "^2.0.1", + "aud": "^2.0.4", + "ls-engines": "^0.9.3", "rimraf": "^3.0.2", "run-parallel": "^1.2.0", "spawn-sync": "^2.0.0", - "standard": "^14.3.1", + "standard": "^14.3.4", "tap": "^16.3.0" }, "license": "Apache-2.0", "repository": "jslicense/licensee.js", "scripts": { "licenses": "./licensee --errors-only", - "lint": "standard index.js licensee test/**/test.js", + "lint": "standard index.js licensee tests/**/*.js", "pretest": "npm run lint", + "postlint": "ls-engines --current", "tests-only": "tap --no-check-coverage tests/unit.test.js tests/**/test.js", "test": "npm run tests-only", "posttest": "aud --production" }, "engines": { - "node": ">= 14.17" + "node": "^18.12 || ^20.9 || >= 22.7" } } diff --git a/tests/blue-oak-misspelled/package.json b/tests/blue-oak-misspelled/package.json new file mode 100644 index 0000000..a6e4166 --- /dev/null +++ b/tests/blue-oak-misspelled/package.json @@ -0,0 +1,4 @@ +{ + "name": "blue-oak-misspelled", + "private": true +} diff --git a/tests/blue-oak-misspelled/test.js b/tests/blue-oak-misspelled/test.js new file mode 100644 index 0000000..65537c7 --- /dev/null +++ b/tests/blue-oak-misspelled/test.js @@ -0,0 +1,5 @@ +var tap = require('tap') + +var results = require('../run')(['--blueoak=foobar'], __dirname) + +tap.equal(results.status, 1) diff --git a/tests/osi-fail/.licensee.json b/tests/osi-fail/.licensee.json new file mode 100644 index 0000000..de583c2 --- /dev/null +++ b/tests/osi-fail/.licensee.json @@ -0,0 +1,6 @@ +{ + "licenses": { + "osi": true + }, + "packages": {} +} diff --git a/tests/osi-fail/node_modules/.package-lock.json b/tests/osi-fail/node_modules/.package-lock.json new file mode 100644 index 0000000..7b49a7d --- /dev/null +++ b/tests/osi-fail/node_modules/.package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "osi-fail", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/cc0-1.0-licensed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cc0-1.0-licensed/-/cc0-1.0-licensed-1.0.0.tgz", + "integrity": "sha512-oN9eCj+L5YQ+O+sEDe8ovuZu/rTV/w7v7rfL+It/6Jz08alTbBnzIwAHJihhgMMWSppeC3Wqsz35jJbQb6Am/A==", + "license": "CC0-1.0" + } + } +} diff --git a/tests/osi-fail/node_modules/cc0-1.0-licensed/index.js b/tests/osi-fail/node_modules/cc0-1.0-licensed/index.js new file mode 100644 index 0000000..f659017 --- /dev/null +++ b/tests/osi-fail/node_modules/cc0-1.0-licensed/index.js @@ -0,0 +1 @@ +throw new Error('Do not require cc0-1.0-licensed. It is just for testing license metadata.') diff --git a/tests/osi-fail/node_modules/cc0-1.0-licensed/package.json b/tests/osi-fail/node_modules/cc0-1.0-licensed/package.json new file mode 100644 index 0000000..f8b4c07 --- /dev/null +++ b/tests/osi-fail/node_modules/cc0-1.0-licensed/package.json @@ -0,0 +1,8 @@ +{ + "name": "cc0-1.0-licensed", + "version": "1.0.0", + "description": "an empty package that is CC0-1.0-licensed", + "author": "Kyle E. Mitchell (https://kemitchell.com/)", + "repository": "jslicense/cc0-1.0-licensed.js", + "license": "CC0-1.0" +} diff --git a/tests/osi-fail/package.json b/tests/osi-fail/package.json new file mode 100644 index 0000000..1c1e03f --- /dev/null +++ b/tests/osi-fail/package.json @@ -0,0 +1,7 @@ +{ + "name": "osi-fail", + "dependencies": { + "cc0-1.0-licensed": "1.0.0" + }, + "private": true +} diff --git a/tests/osi-fail/test.js b/tests/osi-fail/test.js new file mode 100644 index 0000000..7758d8f --- /dev/null +++ b/tests/osi-fail/test.js @@ -0,0 +1,5 @@ +var tap = require('tap') + +var results = require('../run')([], __dirname) + +tap.equal(results.status, 1) diff --git a/tests/osi-flag-fail/node_modules/.package-lock.json b/tests/osi-flag-fail/node_modules/.package-lock.json new file mode 100644 index 0000000..f21ec18 --- /dev/null +++ b/tests/osi-flag-fail/node_modules/.package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "osi-flag-pass", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/cc0-1.0-licensed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cc0-1.0-licensed/-/cc0-1.0-licensed-1.0.0.tgz", + "integrity": "sha512-oN9eCj+L5YQ+O+sEDe8ovuZu/rTV/w7v7rfL+It/6Jz08alTbBnzIwAHJihhgMMWSppeC3Wqsz35jJbQb6Am/A==", + "license": "CC0-1.0" + } + } +} diff --git a/tests/osi-flag-fail/node_modules/cc0-1.0-licensed/index.js b/tests/osi-flag-fail/node_modules/cc0-1.0-licensed/index.js new file mode 100644 index 0000000..f659017 --- /dev/null +++ b/tests/osi-flag-fail/node_modules/cc0-1.0-licensed/index.js @@ -0,0 +1 @@ +throw new Error('Do not require cc0-1.0-licensed. It is just for testing license metadata.') diff --git a/tests/osi-flag-fail/node_modules/cc0-1.0-licensed/package.json b/tests/osi-flag-fail/node_modules/cc0-1.0-licensed/package.json new file mode 100644 index 0000000..f8b4c07 --- /dev/null +++ b/tests/osi-flag-fail/node_modules/cc0-1.0-licensed/package.json @@ -0,0 +1,8 @@ +{ + "name": "cc0-1.0-licensed", + "version": "1.0.0", + "description": "an empty package that is CC0-1.0-licensed", + "author": "Kyle E. Mitchell (https://kemitchell.com/)", + "repository": "jslicense/cc0-1.0-licensed.js", + "license": "CC0-1.0" +} diff --git a/tests/osi-flag-fail/package.json b/tests/osi-flag-fail/package.json new file mode 100644 index 0000000..177244c --- /dev/null +++ b/tests/osi-flag-fail/package.json @@ -0,0 +1,7 @@ +{ + "name": "osi-flag-pass", + "dependencies": { + "cc0-1.0-licensed": "1.0.0" + }, + "private": true +} diff --git a/tests/osi-flag-fail/test.js b/tests/osi-flag-fail/test.js new file mode 100644 index 0000000..29edd29 --- /dev/null +++ b/tests/osi-flag-fail/test.js @@ -0,0 +1,5 @@ +var tap = require('tap') + +var results = require('../run')(['--osi'], __dirname) + +tap.equal(results.status, 1) diff --git a/tests/osi-flag-pass/package.json b/tests/osi-flag-pass/package.json new file mode 100644 index 0000000..e740b8d --- /dev/null +++ b/tests/osi-flag-pass/package.json @@ -0,0 +1,7 @@ +{ + "name": "osi-flag-pass", + "dependencies": { + "gpl-2.0-licensed": "1.0.0" + }, + "private": true +} diff --git a/tests/osi-flag-pass/test.js b/tests/osi-flag-pass/test.js new file mode 100644 index 0000000..a880854 --- /dev/null +++ b/tests/osi-flag-pass/test.js @@ -0,0 +1,5 @@ +var tap = require('tap') + +var results = require('../run')(['--osi'], __dirname) + +tap.equal(results.status, 0) diff --git a/tests/osi-pass/package.json b/tests/osi-pass/package.json index ac0f1f9..75ff404 100644 --- a/tests/osi-pass/package.json +++ b/tests/osi-pass/package.json @@ -1,5 +1,5 @@ { - "name": "blue-oak-fail", + "name": "osi-pass", "dependencies": { "gpl-2.0-licensed": "1.0.0" }, diff --git a/tests/symlinked-node-modules/.licensee.json b/tests/symlinked-node-modules/.licensee.json new file mode 100644 index 0000000..12c1922 --- /dev/null +++ b/tests/symlinked-node-modules/.licensee.json @@ -0,0 +1,13 @@ +{ + "licenses": { + "spdx": [ + "Apache-2.0" + ] + }, + "packages": {}, + "ignore": [ + { + "prefix": "mit-" + } + ] +} diff --git a/tests/symlinked-node-modules/node_modules/mit-licensed/index.js b/tests/symlinked-node-modules/node_modules/mit-licensed/index.js new file mode 100644 index 0000000..d0e9332 --- /dev/null +++ b/tests/symlinked-node-modules/node_modules/mit-licensed/index.js @@ -0,0 +1 @@ +throw new Error('Do not require mit-licensed. It is just for testing license metadata.') diff --git a/tests/symlinked-node-modules/node_modules/mit-licensed/package.json b/tests/symlinked-node-modules/node_modules/mit-licensed/package.json new file mode 100644 index 0000000..dbb0145 --- /dev/null +++ b/tests/symlinked-node-modules/node_modules/mit-licensed/package.json @@ -0,0 +1,8 @@ +{ + "name": "mit-licensed", + "version": "1.0.0", + "description": "an empty package that is MIT-licensed", + "author": "Kyle E. Mitchell (https://kemitchell.com/)", + "repository": "jslicense/mit-licensed.js", + "license": "MIT" +} diff --git a/tests/symlinked-node-modules/package-lock.json b/tests/symlinked-node-modules/package-lock.json new file mode 100644 index 0000000..1b43897 --- /dev/null +++ b/tests/symlinked-node-modules/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "allowed", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "mit-licensed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mit-licensed/-/mit-licensed-1.0.0.tgz", + "integrity": "sha1-/YBXPYPQBMezBoFz2z6MRB13A/k=" + } + } +} diff --git a/tests/symlinked-node-modules/package.json b/tests/symlinked-node-modules/package.json new file mode 100644 index 0000000..cc7f7e2 --- /dev/null +++ b/tests/symlinked-node-modules/package.json @@ -0,0 +1,7 @@ +{ + "name": "allowed", + "dependencies": { + "mit-licensed": "1.0.0" + }, + "private": true +} diff --git a/tests/symlinked-node-modules/test.js b/tests/symlinked-node-modules/test.js new file mode 100644 index 0000000..add2337 --- /dev/null +++ b/tests/symlinked-node-modules/test.js @@ -0,0 +1,5 @@ +var tap = require('tap') + +var results = require('../run')([], __dirname) + +tap.equal(results.status, 0) diff --git a/tests/unlicensed-subdependency/test.js b/tests/unlicensed-subdependency/test.js index 9868d05..7980933 100644 --- a/tests/unlicensed-subdependency/test.js +++ b/tests/unlicensed-subdependency/test.js @@ -1,26 +1,15 @@ var tap = require('tap') -var results = require('../run')([], __dirname) +var results = require('../run')(['--ndjson'], __dirname) tap.equal(results.status, 1) -tap.equal( - results.stdout.trim(), - [ - 'mit-licensed-depends-on-not-licensed@1.0.1', - ' NOT APPROVED', - ' Terms: MIT', - ' Repository: jslicense/mit-licensed-depends-on-not-licensed.js', - ' Homepage: None listed', - ' Author: Kyle E. Mitchell (https://kemitchell.com/)', - ' Contributors: None listed', - '', - 'not-licensed@1.0.0', - ' NOT APPROVED', - ' Terms: Invalid license metadata', - ' Repository: jslicense/not-licensed.js', - ' Homepage: None listed', - ' Author: Kyle E. Mitchell (https://kemitchell.com/)', - ' Contributors: None listed' - ].join('\n') +var output = results.stdout.trim().split('\n').map(line => JSON.parse(line)) + +tap.assert( + output.some(result => result.name === 'mit-licensed-depends-on-not-licensed' && result.approved === false) +) + +tap.assert( + output.some(result => result.name === 'not-licensed' && result.approved === false) )