Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add package-lock modes to query and audit #6732

Merged
merged 2 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/lib/content/commands/npm-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ vulnerability is found. It may be useful in CI environments to include the
will cause the command to fail. This option does not filter the report
output, it simply changes the command's failure threshold.

### Package lock

By default npm requires a package-lock or shrinkwrap in order to run the
audit. You can bypass the package lock with `--no-package-lock` but be
aware the results may be different with every run, since npm will
re-build the dependency tree each time.

### Audit Signatures

To ensure the integrity of packages you download from the public npm registry, or any registry that supports signatures, you can verify the registry signatures of downloaded packages using the npm CLI.
Expand Down
13 changes: 13 additions & 0 deletions docs/lib/content/commands/npm-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ npm query ":type(git)" | jq 'map(.name)' | xargs -I {} npm why {}
},
...
```
### Package lock only mode

If package-lock-only is enabled, only the information in the package
lock (or shrinkwrap) is loaded. This means that information from the
package.json files of your dependencies will not be included in the
result set (e.g. description, homepage, engines).

### Package lock only mode

If package-lock-only is enabled, only the information in the package
lock (or shrinkwrap) is loaded. This means that information from the
package.json files of your dependencies will not be included in the
result set (e.g. description, homepage, engines).

### Configuration

Expand Down
6 changes: 5 additions & 1 deletion lib/commands/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ class Audit extends ArboristWorkspaceCmd {
'force',
'json',
'package-lock-only',
'package-lock',
'omit',
'foreground-scripts',
'ignore-scripts',
Expand Down Expand Up @@ -439,6 +440,10 @@ class Audit extends ArboristWorkspaceCmd {
}

async auditAdvisories (args) {
const fix = args[0] === 'fix'
if (this.npm.config.get('package-lock') === false && fix) {
throw this.usageError('fix can not be used without a package-lock')
}
const reporter = this.npm.config.get('json') ? 'json' : 'detail'
const Arborist = require('@npmcli/arborist')
const opts = {
Expand All @@ -450,7 +455,6 @@ class Audit extends ArboristWorkspaceCmd {
}

const arb = new Arborist(opts)
const fix = args[0] === 'fix'
await arb.audit({ fix })
if (fix) {
await reifyFinish(this.npm, arb)
Expand Down
15 changes: 14 additions & 1 deletion lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const { resolve } = require('path')
const BaseCommand = require('../base-command.js')
const log = require('../utils/log-shim.js')

class QuerySelectorItem {
constructor (node) {
Expand Down Expand Up @@ -48,6 +49,7 @@ class Query extends BaseCommand {
'workspace',
'workspaces',
'include-workspace-root',
'package-lock-only',
]

get parsedResponse () {
Expand All @@ -64,7 +66,18 @@ class Query extends BaseCommand {
forceActual: true,
}
const arb = new Arborist(opts)
const tree = await arb.loadActual(opts)
let tree
if (this.npm.config.get('package-lock-only')) {
try {
tree = await arb.loadVirtual()
} catch (err) {
log.verbose('loadVirtual', err.stack)
/* eslint-disable-next-line max-len */
throw this.usageError('A package lock or shrinkwrap file is required in package-lock-only mode')
lukekarrys marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
tree = await arb.loadActual(opts)
}
const items = await tree.querySelectorAll(args[0], this.npm.flatOptions)
this.buildResponse(items)

Expand Down
48 changes: 48 additions & 0 deletions tap-snapshots/test/lib/commands/query.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,54 @@ exports[`test/lib/commands/query.js TAP linked node > should return linked node
]
`

exports[`test/lib/commands/query.js TAP package-lock-only with package lock > should return valid response with only lock info 1`] = `
[
{
"name": "project",
"dependencies": {
"a": "^1.0.0"
},
"pkgid": "project@",
"location": "",
"path": "{CWD}/prefix",
"realpath": "{CWD}/prefix",
"resolved": null,
"from": [],
"to": [
"node_modules/a"
],
"dev": false,
"inBundle": false,
"deduped": false,
"overridden": false,
"queryContext": {}
},
{
"version": "1.2.3",
"resolved": "https://dummy.npmjs.org/a/-/a-1.2.3.tgz",
"integrity": "sha512-dummy",
"engines": {
"node": ">=14.17"
},
"name": "a",
"_id": "a@1.2.3",
"pkgid": "a@1.2.3",
"location": "node_modules/a",
"path": "{CWD}/prefix/node_modules/a",
"realpath": "{CWD}/prefix/node_modules/a",
"from": [
""
],
"to": [],
"dev": false,
"inBundle": false,
"deduped": false,
"overridden": false,
"queryContext": {}
}
]
`

exports[`test/lib/commands/query.js TAP recursive tree > should return everything in the tree, accounting for recursion 1`] = `
[
{
Expand Down
6 changes: 4 additions & 2 deletions tap-snapshots/test/lib/docs.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2526,7 +2526,7 @@ npm audit [fix|signatures]

Options:
[--audit-level <info|low|moderate|high|critical|none>] [--dry-run] [-f|--force]
[--json] [--package-lock-only]
[--json] [--package-lock-only] [--no-package-lock]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[--foreground-scripts] [--ignore-scripts]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
Expand All @@ -2543,6 +2543,7 @@ npm audit [fix|signatures]
#### \`force\`
#### \`json\`
#### \`package-lock-only\`
#### \`package-lock\`
#### \`omit\`
#### \`foreground-scripts\`
#### \`ignore-scripts\`
Expand Down Expand Up @@ -3766,7 +3767,7 @@ npm query <selector>
Options:
[-g|--global]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]
[-ws|--workspaces] [--include-workspace-root] [--package-lock-only]

Run "npm help query" for more info

Expand All @@ -3778,6 +3779,7 @@ npm query <selector>
#### \`workspace\`
#### \`workspaces\`
#### \`include-workspace-root\`
#### \`package-lock-only\`
`

exports[`test/lib/docs.js TAP usage rebuild > must match snapshot 1`] = `
Expand Down
12 changes: 12 additions & 0 deletions test/lib/commands/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,18 @@ t.test('audit fix - bulk endpoint', async t => {
)
})

t.test('audit fix no package lock', async t => {
const { npm } = await loadMockNpm(t, {
config: {
'package-lock': false,
},
})
await t.rejects(
npm.exec('audit', ['fix']),
{ code: 'EUSAGE' }
)
})

t.test('completion', async t => {
const { audit } = await loadMockNpm(t, { command: 'audit' })
t.test('fix', async t => {
Expand Down
58 changes: 58 additions & 0 deletions test/lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,61 @@ t.test('global', async t => {
await npm.exec('query', ['[name=lorem]'])
t.matchSnapshot(joinedOutput(), 'should return global package')
})

t.test('package-lock-only', t => {
t.test('no package lock', async t => {
const { npm } = await loadMockNpm(t, {
config: {
'package-lock-only': true,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
},
}),
},
})
await t.rejects(npm.exec('query', [':root, :root > *']), { code: 'EUSAGE' })
})

t.test('with package lock', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
'package-lock-only': true,
},
prefixDir: {
'package.json': JSON.stringify({
name: 'project',
dependencies: {
a: '^1.0.0',
},
}),
'package-lock.json': JSON.stringify({
name: 'project',
lockfileVersion: 3,
requires: true,
packages: {
'': {
dependencies: {
a: '^1.0.0',
},
},
'node_modules/a': {
version: '1.2.3',
resolved: 'https://dummy.npmjs.org/a/-/a-1.2.3.tgz',
integrity: 'sha512-dummy',
engines: {
node: '>=14.17',
},
},
},
}),
},
})
await npm.exec('query', ['*'])
t.matchSnapshot(joinedOutput(), 'should return valid response with only lock info')
})
t.end()
})
10 changes: 9 additions & 1 deletion workspaces/arborist/lib/arborist/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ module.exports = cls => class Auditor extends cls {
options = { ...this.options, ...options }

process.emit('time', 'audit')
const tree = await this.loadVirtual()
let tree
if (options.packageLock === false) {
// build ideal tree
await this.loadActual(options)
await this.buildIdealTree()
tree = this.idealTree
} else {
tree = await this.loadVirtual()
}
if (this[_workspaces] && this[_workspaces].length) {
options.filterSet = this.workspaceDependencySet(
tree,
Expand Down
9 changes: 9 additions & 0 deletions workspaces/arborist/test/arborist/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ t.test('audit finds the bad deps', async t => {
t.equal(report.size, 2)
})

t.test('no package lock finds no bad deps', async t => {
const path = resolve(fixtures, 'deprecated-dep')
t.teardown(auditResponse(resolve(fixtures, 'audit-nyc-mkdirp/audit.json')))
const arb = newArb(path, { packageLock: false })
const report = await arb.audit()
t.equal(report.topVulns.size, 0)
t.equal(report.size, 0)
})

t.test('audit fix reifies out the bad deps', async t => {
const path = fixture(t, 'deprecated-dep')
t.teardown(auditResponse(resolve(fixtures, 'audit-nyc-mkdirp/audit.json')))
Expand Down