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, or a
specific number of entries.
  • Loading branch information
wraithgar committed Dec 14, 2022
1 parent 7018b3d commit b00d21e
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 0 deletions.
22 changes: 22 additions & 0 deletions lib/base-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,28 @@ class BaseCommand {
})
}

// Compare the number of entries with what was expected
checkExpected (entries) {
const expected = this.npm.config.get('expect-entries')
if (expected === null) {
// By default we do nothing
return
}
if (typeof expected === 'number') {
if (entries !== expected) {
process.exitCode = 1
}
return
}
// entries is boolean
if (expected === true && !entries) {
process.exitCode = 1
} else if (expected === false && !!entries) {
process.exitCode = 1
}
// TODO `>5` or `<=6` which will require a custom Config type
}

async execWorkspaces (args, filters) {
throw Object.assign(new Error('This command does not support workspaces.'), {
code: 'ENOWORKSPACES',
Expand Down
3 changes: 3 additions & 0 deletions lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Query extends BaseCommand {
'workspace',
'workspaces',
'include-workspace-root',
'expect-entries',
]

get parsedResponse () {
Expand All @@ -67,6 +68,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 @@ -89,6 +91,7 @@ class Query extends BaseCommand {
}
this.buildResponse(items)
}
this.checkExpected(this.#response.length)
this.npm.output(this.parsedResponse)
}

Expand Down
10 changes: 10 additions & 0 deletions lib/utils/config/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,16 @@ define('engine-strict', {
flatten,
})

define('expect-entries', {
default: null,
type: [null, Boolean, Number],
description: `
Tells npm how many entries to expect from the command. Can be either
true (expect some entries), false (expect no entries), or a number to match
exactly.
`,
})

define('fetch-retries', {
default: 2,
type: Number,
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 @@ -48,6 +48,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 @@ -201,6 +202,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
13 changes: 13 additions & 0 deletions tap-snapshots/test/lib/docs.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,15 @@ Node.js version.
This can be overridden by setting the \`--force\` flag.
#### \`expect-entries\`
* Default: null
* Type: null, Boolean, or Number
Tells npm how many entries to expect from the command. Can be either true
(expect some entries), false (expect no entries), or a number to match
exactly.
#### \`fetch-retries\`
* Default: 2
Expand Down Expand Up @@ -2150,6 +2159,7 @@ Array [
"dry-run",
"editor",
"engine-strict",
"expect-entries",
"fetch-retries",
"fetch-retry-factor",
"fetch-retry-maxtimeout",
Expand Down Expand Up @@ -2389,6 +2399,7 @@ Array [

exports[`test/lib/docs.js TAP config > keys that are not flattened 1`] = `
Array [
"expect-entries",
"init-author-email",
"init-author-name",
"init-author-url",
Expand Down Expand Up @@ -3730,6 +3741,7 @@ Options:
[-g|--global]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]
[--no-expect-entries|--expect-entries <expect-entries>]
Run "npm help query" for more info
Expand All @@ -3741,6 +3753,7 @@ npm query <selector>
#### \`workspace\`
#### \`workspaces\`
#### \`include-workspace-root\`
#### \`expect-entries\`
`

exports[`test/lib/docs.js TAP usage rebuild > must match snapshot 1`] = `
Expand Down
70 changes: 70 additions & 0 deletions test/lib/commands/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,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 @@ -197,3 +198,72 @@ t.test('global', async t => {
await npm.exec('query', ['[name=lorem]'])
t.matchSnapshot(joinedOutput(), 'should return global package')
})

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 { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-entries', false)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
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-entries', 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-entries', 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 { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-entries', true)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.ok(process.exitCode, 'exits with code')
})
t.test('number, matches', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-entries', 1)
await npm.exec('query', ['#a'])
t.not(joinedOutput(), '[]', 'has entries')
t.notOk(process.exitCode, 'exits without code')
})
t.test('number, does not match', async t => {
const { npm, joinedOutput } = await loadMockNpm(t, {
prefixDir,
})
npm.config.set('expect-entries', 1)
await npm.exec('query', ['#b'])
t.equal(joinedOutput(), '[]', 'does not have entries')
t.ok(process.exitCode, 'exits with code')
})
t.end()
})

0 comments on commit b00d21e

Please sign in to comment.