Skip to content

Commit

Permalink
feat: add --expect-entries to npm query
Browse files Browse the repository at this point in the history
This will allow users to tell npm whether or not to exit with an exit
code depending on if the command had any resulting entries or not.
  • Loading branch information
wraithgar committed Feb 6, 2024
1 parent d04111d commit 1d7de6a
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 0 deletions.
19 changes: 19 additions & 0 deletions lib/base-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { relative } = require('path')
const { definitions } = require('@npmcli/config/lib/definitions')
const getWorkspaces = require('./workspaces/get-workspaces.js')
const { aliases: cmdAliases } = require('./utils/cmd-list')
const log = require('./utils/log-shim.js')

class BaseCommand {
static workspaces = false
Expand Down Expand Up @@ -142,6 +143,24 @@ class BaseCommand {
return this.exec(args)
}

// Compare the number of entries with what was expected
checkExpected (entries) {
if (!this.npm.config.isDefault('expect-results')) {
const expected = this.npm.config.get('expect-results')
if (!!entries !== !!expected) {
log.warn(this.name, `Expected ${expected ? '' : 'no '}results, got ${entries}`)
process.exitCode = 1
}
} else if (!this.npm.config.isDefault('expect-result-count')) {
const expected = this.npm.config.get('expect-result-count')
if (expected !== entries) {
/* eslint-disable-next-line max-len */
log.warn(this.name, `Expected ${expected} result${expected === 1 ? '' : 's'}, got ${entries}`)
process.exitCode = 1
}
}
}

async setWorkspaces () {
const includeWorkspaceRoot = this.isArboristCmd
? false
Expand Down
3 changes: 3 additions & 0 deletions lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Query extends BaseCommand {
'workspaces',
'include-workspace-root',
'package-lock-only',
'expect-results',
]

get parsedResponse () {
Expand Down Expand Up @@ -81,6 +82,7 @@ class Query extends BaseCommand {
const items = await tree.querySelectorAll(args[0], this.npm.flatOptions)
this.buildResponse(items)

this.checkExpected(this.#response.length)
this.npm.output(this.parsedResponse)
}

Expand All @@ -104,6 +106,7 @@ class Query extends BaseCommand {
}
this.buildResponse(items)
}
this.checkExpected(this.#response.length)
this.npm.output(this.parsedResponse)
}

Expand Down
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/commands/config.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna
"dry-run": false,
"editor": "{EDITOR}",
"engine-strict": false,
"expect-entries": null,
"fetch-retries": 2,
"fetch-retry-factor": 10,
"fetch-retry-maxtimeout": 60000,
Expand Down Expand Up @@ -207,6 +208,7 @@ diff-unified = 3
dry-run = false
editor = "{EDITOR}"
engine-strict = false
expect-entries = null
fetch-retries = 2
fetch-retry-factor = 10
fetch-retry-maxtimeout = 60000
Expand Down
83 changes: 83 additions & 0 deletions test/lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ t.test('recursive tree', async t => {
await npm.exec('query', ['*'])
t.matchSnapshot(joinedOutput(), 'should return everything in the tree, accounting for recursion')
})

t.test('workspace query', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
config: {
Expand Down Expand Up @@ -237,3 +238,85 @@ t.test('package-lock-only', t => {
})
t.end()
})

t.test('expect entries', t => {
const { exitCode } = process
t.afterEach(() => process.exitCode = exitCode)
const prefixDir = {
node_modules: {
a: { name: 'a', version: '1.0.0' },
},
'package.json': JSON.stringify({
name: 'project',
dependencies: { a: '^1.0.0' },
}),
}
t.test('false, has entries', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', false)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.same(logs.warn, [['query', 'Expected no results, got 1']])
t.ok(process.exitCode, 'exits with code')
})
t.test('false, no entries', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', false)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('true, has entries', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', true)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('true, no entries', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-results', true)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.same(logs.warn, [['query', 'Expected results, got 0']])
t.ok(process.exitCode, 'exits with code')
})
t.test('count, matches', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-result-count', 1)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('count 1, does not match', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-result-count', 1)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.same(logs.warn, [['query', 'Expected 1 result, got 0']])
t.ok(process.exitCode, 'exits with code')
})
t.test('count 3, does not match', async t => {
const { logs, npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-result-count', 3)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.same(logs.warn, [['query', 'Expected 3 results, got 0']])
t.ok(process.exitCode, 'exits with code')
})
t.end()
})
20 changes: 20 additions & 0 deletions workspaces/config/lib/definitions/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,26 @@ define('engine-strict', {
flatten,
})

define('expect-results', {
default: null,
type: [null, Boolean],
exclusive: ['expect-result-count'],
description: `
Tells npm whether or not to expect results from the command.
Can be either true (expect some results) or false (expect no results).
`,
})

define('expect-result-count', {
default: null,
type: [null, Number],
hint: '<count>',
exclusive: ['expect-results'],
description: `
Tells to expect a specific number of results from the command.
`,
})

define('fetch-retries', {
default: 2,
type: Number,
Expand Down

0 comments on commit 1d7de6a

Please sign in to comment.