Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
audit: Add new audit command
PR-URL: #20389
Credit: @iarna
Reviewed-By: @zkat
  • Loading branch information
iarna authored and zkat committed Apr 20, 2018
1 parent 5e28404 commit 8c77dde74a9d8f9007667cd1732c3329e0d52617
Showing with 396 additions and 23 deletions.
  1. +83 −0 lib/audit.js
  2. +1 −0 lib/config/cmd-list.js
  3. +2 −0 lib/config/defaults.js
  4. +32 −14 lib/install.js
  5. +260 −0 lib/install/audit.js
  6. +2 −0 lib/shrinkwrap.js
  7. +15 −8 lib/utils/error-message.js
  8. +1 −1 package.json
@@ -0,0 +1,83 @@
'use strict'
const fs = require('graceful-fs')
const Bluebird = require('bluebird')
const audit = require('./install/audit.js')
const npm = require('./npm.js')
const log = require('npmlog')
const parseJson = require('json-parse-better-errors')

const readFile = Bluebird.promisify(fs.readFile)

module.exports = auditCmd

auditCmd.usage =
'npm audit\n'

auditCmd.completion = function (opts, cb) {
const argv = opts.conf.argv.remain

switch (argv[2]) {
case 'audit':
return cb(null, [])
default:
return cb(new Error(argv[2] + ' not recognized'))
}
}

function maybeReadFile (name) {
const file = `${npm.prefix}/${name}`
return readFile(file)
.then((data) => {
try {
return parseJson(data)
} catch (ex) {
ex.code = 'EJSONPARSE'
throw ex
}
})
.catch({code: 'ENOENT'}, () => null)
.catch(ex => {
ex.file = file
throw ex
})
}

function auditCmd (args, cb) {
return Bluebird.all([
maybeReadFile('npm-shrinkwrap.json'),
maybeReadFile('package-lock.json'),
maybeReadFile('package.json')
]).spread((shrinkwrap, lockfile, pkgJson) => {
const sw = shrinkwrap || lockfile
if (!pkgJson) {
const err = new Error('No package.json found: Cannot audit a project without a package.json')
err.code = 'EAUDITNOPJSON'
throw err
}
if (!sw) {
const err = new Error('Neither npm-shrinkwrap.json nor package-lock.json found: Cannot audit a project without a lockfile')
err.code = 'EAUDITNOLOCK'
throw err
} else if (shrinkwrap && lockfile) {
log.warn('audit', 'Both npm-shrinkwrap.json and package-lock.json exist, using npm-shrinkwrap.json.')
}
const requires = Object.assign(
{},
(pkgJson && pkgJson.dependencies) || {},
(pkgJson && pkgJson.devDependencies) || {}
)
return audit.generate(sw, requires)
}).then((auditReport) => {
return audit.submitForFullReport(auditReport)
}).catch(err => {
if (err.statusCode >= 400) {
const ne = new Error(`Your configured registry (${npm.config.get('registry')}) does not support audit requests.`)
ne.code = 'ENOAUDIT'
ne.wrapped = err
throw ne
}
throw err
}).then((auditResult) => {
return audit.printFullReport(auditResult)
}).asCallback(cb)
}
@@ -83,6 +83,7 @@ var cmdList = [
'shrinkwrap',
'token',
'profile',
'audit',

'help',
'help-search',
@@ -109,6 +109,7 @@ Object.defineProperty(exports, 'defaults', {get: function () {
'allow-same-version': false,
'always-auth': false,
also: null,
audit: true,
'auth-type': 'legacy',

'bin-links': true,
@@ -252,6 +253,7 @@ exports.types = {
'allow-same-version': Boolean,
'always-auth': Boolean,
also: [null, 'dev', 'development'],
audit: Boolean,
'auth-type': ['legacy', 'sso', 'saml', 'oauth'],
'bin-links': Boolean,
browser: [null, String],
@@ -137,6 +137,7 @@ var validateTree = require('./install/validate-tree.js')
var validateArgs = require('./install/validate-args.js')
var saveRequested = require('./install/save.js').saveRequested
var saveShrinkwrap = require('./install/save.js').saveShrinkwrap
var audit = require('./install/audit.js')
var getSaveType = require('./install/save.js').getSaveType
var doSerialActions = require('./install/actions.js').doSerial
var doReverseSerialActions = require('./install/actions.js').doReverseSerial
@@ -238,6 +239,7 @@ function Installer (where, dryrun, args, opts) {
this.link = opts.link != null ? opts.link : npm.config.get('link')
this.saveOnlyLock = opts.saveOnlyLock
this.global = opts.global != null ? opts.global : this.where === path.resolve(npm.globalDir, '..')
this.audit = npm.config.get('audit') && !this.global
this.started = Date.now()
}
Installer.prototype = {}
@@ -298,7 +300,9 @@ Installer.prototype.run = function (_cb) {
[this, this.finishTracker, 'generateActionsToTake'],

[this, this.debugActions, 'diffTrees', 'differences'],
[this, this.debugActions, 'decomposeActions', 'todo'])
[this, this.debugActions, 'decomposeActions', 'todo'],
[this, this.startAudit]
)

if (this.packageLockOnly) {
postInstallSteps.push(
@@ -626,6 +630,16 @@ Installer.prototype.runPostinstallTopLevelLifecycles = function (cb) {
chain(steps, cb)
}

Installer.prototype.startAudit = function (cb) {
if (!this.audit) return cb()
this.auditSubmission = Bluebird.try(() => {
return audit.generateFromInstall(this.idealTree, this.differences, this.args, this.remove)
}).then((auditData) => {
return audit.submitForInstallReport(auditData)
}).catch(_ => {})
cb()
}

Installer.prototype.saveToDependencies = function (cb) {
validate('F', arguments)
if (this.failing) return cb()
@@ -748,16 +762,21 @@ Installer.prototype.printInstalled = function (cb) {
diffs.push(['remove', r])
})
}
if (npm.config.get('json')) {
return this.printInstalledForJSON(diffs, cb)
} else if (npm.config.get('parseable')) {
return this.printInstalledForParseable(diffs, cb)
} else {
return this.printInstalledForHuman(diffs, cb)
}
return Bluebird.try(() => {
return this.auditSubmission && this.auditSubmission.catch(() => null)
}).then((auditResult) => {
// maybe write audit report w/ hash of pjson & shrinkwrap for later reading by `npm audit`
if (npm.config.get('json')) {
return this.printInstalledForJSON(diffs, auditResult)
} else if (npm.config.get('parseable')) {
return this.printInstalledForParseable(diffs, auditResult)
} else {
return this.printInstalledForHuman(diffs, auditResult)
}
}).asCallback(cb)
}

Installer.prototype.printInstalledForHuman = function (diffs, cb) {
Installer.prototype.printInstalledForHuman = function (diffs, auditResult) {
var removed = 0
var added = 0
var updated = 0
@@ -825,7 +844,7 @@ Installer.prototype.printInstalledForHuman = function (diffs, cb) {
report += ' in ' + ((Date.now() - this.started) / 1000) + 's'

output(report)
return cb()
return auditResult && audit.printInstallReport(auditResult)

function packages (num) {
return num + ' package' + (num > 1 ? 's' : '')
@@ -849,14 +868,15 @@ Installer.prototype.printInstalledForHuman = function (diffs, cb) {
}
}

Installer.prototype.printInstalledForJSON = function (diffs, cb) {
Installer.prototype.printInstalledForJSON = function (diffs, auditResult) {
var result = {
added: [],
removed: [],
updated: [],
moved: [],
failed: [],
warnings: [],
audit: auditResult,
elapsed: Date.now() - this.started
}
var self = this
@@ -887,7 +907,6 @@ Installer.prototype.printInstalledForJSON = function (diffs, cb) {
}
})
output(JSON.stringify(result, null, 2))
cb()

function flattenMessage (msg) {
return msg.map(function (logline) { return logline.slice(1).join(' ') }).join('\n')
@@ -911,7 +930,7 @@ Installer.prototype.printInstalledForJSON = function (diffs, cb) {
}
}

Installer.prototype.printInstalledForParseable = function (diffs, cb) {
Installer.prototype.printInstalledForParseable = function (diffs) {
var self = this
diffs.forEach(function (action) {
var mutation = action[0]
@@ -929,7 +948,6 @@ Installer.prototype.printInstalledForParseable = function (diffs, cb) {
(previousVersion || '') + '\t' +
(previousPath || ''))
})
return cb()
}

Installer.prototype.debugActions = function (name, actionListName, cb) {

0 comments on commit 8c77dde

Please sign in to comment.