From 1432cca95af9da074ce4d0d020ef708969634298 Mon Sep 17 00:00:00 2001 From: Tierney Cyren Date: Tue, 22 Jun 2021 20:12:58 +0000 Subject: [PATCH 1/6] feat: add license checking with json output --- lib/audit.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/audit.js b/lib/audit.js index 54480d1f0cbf9..0676eb96daa8a 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -1,5 +1,6 @@ const Arborist = require('@npmcli/arborist') const auditReport = require('npm-audit-report') +const licensee = require('licensee') const reifyFinish = require('./utils/reify-finish.js') const auditError = require('./utils/audit-error.js') const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js') @@ -48,10 +49,15 @@ class Audit extends ArboristWorkspaceCmd { } exec (args, cb) { - this.audit(args).then(() => cb()).catch(cb) + const auditType = this.npm.config.get('audit-type') + if (auditType === 'license') { + this.auditLicenses(args).then(() => cb()).catch(cb) + } else { + this.auditAdvisories(args).then(() => cb()).catch(cb) + } } - async audit (args) { + async auditAdvisories (args) { const reporter = this.npm.config.get('json') ? 'json' : 'detail' const opts = { ...this.npm.flatOptions, @@ -74,6 +80,51 @@ class Audit extends ArboristWorkspaceCmd { this.npm.output(result.report) } } + + async auditLicenses (args) { + const reporter = this.npm.config.get('json') ? 'json' : 'detail' + const config = { // this is extremely limited and should consume user input rather than our own config + "licenses": { + "spdx": [ + "MIT" + ] + } + } + await new Promise((res, rej) => { + const licensesPath = this.npm.prefix + licensee(config, licensesPath, (error, dependencies) => { + if (error) rej(error) + if (reporter === "json") { + this.jsonStringifyOutput(dependencies) + } + res() + }) + }) + + } + + jsonStringifyOutput (dependencies) { + const loggableProperties = [ + 'name', + 'version', + 'approved', + 'license', + 'corrected', + 'repository', + 'homepage', + 'author', + 'contributors' + ] + + const out = {} + for(const dep of dependencies) { + out[dep.name] = {} + for (const prop of loggableProperties) { + out[dep.name][prop] = dep[prop] + } + } + this.npm.output(JSON.stringify(out, null, 2)) + } } module.exports = Audit From 3075d7271039dc5232f0acc9f44275eb6dd84217 Mon Sep 17 00:00:00 2001 From: Tierney Cyren Date: Tue, 22 Jun 2021 20:13:21 +0000 Subject: [PATCH 2/6] test: test for license checking with json output --- test/lib/audit.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/lib/audit.js b/test/lib/audit.js index bb6f06debc51f..84efdf69acf12 100644 --- a/test/lib/audit.js +++ b/test/lib/audit.js @@ -168,6 +168,54 @@ t.test('report endpoint error', t => { t.end() }) +t.test('licenses', t => { + const Audit = require('../../lib/audit.js') + t.test('run audit with license type and output json', t => { + const prefix = t.testdir({ + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + license: 'MIT', + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + license: 'ISC', + }), + }, + }, + }) + + const OUTPUT = [] + const npm = mockNpm({ + prefix: prefix, + command: 'audit', + config: { + "audit-type": "license", + json: true + }, + output: (...msg) => { + OUTPUT.push(msg) + }, + }) + + const audit = new Audit(npm) + + audit.exec([], () => { + t.strictSame(JSON.parse(OUTPUT), { + a: { name: 'a', version: '1.0.0', approved: true, license: 'MIT' }, + b: { name: 'b', version: '1.0.0', approved: false, license: 'ISC' } + }) + t.end() + }) + }) + t.end() +}) + t.test('completion', t => { const Audit = require('../../lib/audit.js') const audit = new Audit({}) From 46a6471f247cd2d2a09d989aea3d4c362206d575 Mon Sep 17 00:00:00 2001 From: Tierney Cyren Date: Tue, 22 Jun 2021 20:21:34 +0000 Subject: [PATCH 3/6] deps: move licensee to dep rather than devDep --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 58ac5d7f8c6c8..f750bc59d820f 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "libnpmsearch": "^3.1.1", "libnpmteam": "^2.0.3", "libnpmversion": "^1.2.1", + "licensee": "^8.2.0", "make-fetch-happen": "^9.0.3", "minipass": "^3.1.3", "minipass-pipeline": "^1.2.4", @@ -196,7 +197,6 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "eslint-plugin-standard": "^5.0.0", - "licensee": "^8.2.0", "tap": "^15.0.9" }, "scripts": { From d88b2623082acb911160c9dec4910ba35b12ecc6 Mon Sep 17 00:00:00 2001 From: Tierney Cyren Date: Tue, 7 Sep 2021 14:08:06 -0400 Subject: [PATCH 4/6] chore: minor style changes --- lib/audit.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/audit.js b/lib/audit.js index 0676eb96daa8a..03a3e3025b5d3 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -52,7 +52,7 @@ class Audit extends ArboristWorkspaceCmd { const auditType = this.npm.config.get('audit-type') if (auditType === 'license') { this.auditLicenses(args).then(() => cb()).catch(cb) - } else { + } else { this.auditAdvisories(args).then(() => cb()).catch(cb) } } @@ -84,23 +84,22 @@ class Audit extends ArboristWorkspaceCmd { async auditLicenses (args) { const reporter = this.npm.config.get('json') ? 'json' : 'detail' const config = { // this is extremely limited and should consume user input rather than our own config - "licenses": { - "spdx": [ - "MIT" - ] + licenses: { + spdx: [ + "MIT" + ] } } - await new Promise((res, rej) => { + await new Promise((resolve, reject) => { const licensesPath = this.npm.prefix licensee(config, licensesPath, (error, dependencies) => { - if (error) rej(error) + if (error) reject(error) if (reporter === "json") { this.jsonStringifyOutput(dependencies) } - res() + resolve() }) }) - } jsonStringifyOutput (dependencies) { @@ -117,7 +116,7 @@ class Audit extends ArboristWorkspaceCmd { ] const out = {} - for(const dep of dependencies) { + for (const dep of dependencies) { out[dep.name] = {} for (const prop of loggableProperties) { out[dep.name][prop] = dep[prop] From f3e183d8b0c44d805ceed5491c4f4d7c2278c5cd Mon Sep 17 00:00:00 2001 From: Tierney Cyren Date: Thu, 9 Sep 2021 16:44:19 -0400 Subject: [PATCH 5/6] feat: some more work on Audit --- lib/audit.js | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/audit.js b/lib/audit.js index 03a3e3025b5d3..58095ca0cd59e 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -1,5 +1,6 @@ const Arborist = require('@npmcli/arborist') const auditReport = require('npm-audit-report') +const rpj = require('read-package-json-fast') const licensee = require('licensee') const reifyFinish = require('./utils/reify-finish.js') const auditError = require('./utils/audit-error.js') @@ -52,7 +53,10 @@ class Audit extends ArboristWorkspaceCmd { const auditType = this.npm.config.get('audit-type') if (auditType === 'license') { this.auditLicenses(args).then(() => cb()).catch(cb) + } if (auditType === 'advisories') { + this.auditAdvisories(args).then(() => cb()).catch(cb) } else { + // we should probably collect the JSON from auditAdvisories and auditLicenses and merge them into a single object/output? this.auditAdvisories(args).then(() => cb()).catch(cb) } } @@ -83,17 +87,21 @@ class Audit extends ArboristWorkspaceCmd { async auditLicenses (args) { const reporter = this.npm.config.get('json') ? 'json' : 'detail' - const config = { // this is extremely limited and should consume user input rather than our own config - licenses: { - spdx: [ - "MIT" - ] - } - } + const packagejson = await rpj(`${this.npm.prefix}/package.json`) + + if (!packagejson.audit) throw new Error("No audit property specified in package.json") + if (!packagejson.audit.licenses) throw new Error("No license specified in the audit property in package.json") + if (Object.keys(packagejson.audit.licenses).length === 0) throw new Error("license property in package.json cannot be empty.") + + const config = await buildLicenseConfig(packagejson) + await new Promise((resolve, reject) => { const licensesPath = this.npm.prefix licensee(config, licensesPath, (error, dependencies) => { if (error) reject(error) + if (reporter === "detail") { + // figure out pretty printing of license data + } if (reporter === "json") { this.jsonStringifyOutput(dependencies) } @@ -126,4 +134,30 @@ class Audit extends ArboristWorkspaceCmd { } } +async function buildLicenseConfig (packagejson) { + const licenses = packagejson.audit.licenses + const licenseConfig = {} + + if (licenses.identifiers) { + licenseConfig.licenses = {} + licenseConfig.licenses.spdx = licenses.identifiers + } + + if (licenses.packages) { + licenseConfig.packages = licenses.packages + } + + if (licenses.corrections === "false") { // prioritize users choice + licenseConfig.corrections = "false" + } else { // default to true for DX + licenseConfig.corrections = "true" + } + + if (licenses.ignore) { + licenseConfig.ignore = licenses.ignore + } + + return licenseConfig +} + module.exports = Audit From 73aed2bc2deddae8959d667062e4ff52b1705f71 Mon Sep 17 00:00:00 2001 From: Tierney Cyren Date: Thu, 9 Sep 2021 16:45:16 -0400 Subject: [PATCH 6/6] chore: add licenses to allow in license check --- package.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package.json b/package.json index f750bc59d820f..ab135766e62fc 100644 --- a/package.json +++ b/package.json @@ -231,5 +231,20 @@ "license": "Artistic-2.0", "engines": { "node": ">=10" + }, + "audit": { + "licenses": { + "identifiers": [ + "MIT", + "ISC", + "CC0-1.0", + "Artistic-2.0", + "BSD-3-Clause", + "Apache-2.0", + "BSD-2-Clause", + "CC-BY-3.0", + "CC-BY-4.0" + ] + } } }