Skip to content

Commit 3d1c84c

Browse files
committed
definitions array, support definition exclusivity
1 parent 2fb6d7a commit 3d1c84c

File tree

11 files changed

+149
-128
lines changed

11 files changed

+149
-128
lines changed

lib/base-cmd.js

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,15 @@ class BaseCommand {
2727
const { aliases: cmdAliases } = require('./utils/cmd-list')
2828
const seenExclusive = new Set()
2929
const wrapWidth = 80
30-
let { description, usage = [''], name, params } = this
30+
const { description, usage = [''], name } = this
3131

32-
let definitionsPool = {}
32+
// Resolve to a definitions array: if the command has its own definitions, use
33+
// those directly; otherwise resolve params from the global definitions pool.
34+
let cmdDefs
3335
if (this.definitions) {
34-
definitionsPool = { ...definitions, ...this.definitions }
35-
// Auto-populate params from definitions if not explicitly set
36-
if (!params && this.definitions) {
37-
params = Object.values(this.definitions).map(def => def.key)
38-
}
39-
} else {
40-
// Don't mutate this.definitions - just use definitions directly
41-
definitionsPool = definitions
36+
cmdDefs = this.definitions
37+
} else if (this.params) {
38+
cmdDefs = this.params.map(p => definitions[p]).filter(Boolean)
4239
}
4340

4441
// If this is a subcommand, prepend parent name
@@ -70,22 +67,22 @@ class BaseCommand {
7067
fullUsage.push(`Run "npm ${name} <subcommand> --help" for more info on a subcommand.`)
7168
}
7269

73-
if (params) {
70+
if (cmdDefs) {
71+
const cmdDefsKeys = new Set(cmdDefs.map(d => d.key))
7472
let results = ''
7573
let line = ''
76-
for (const param of params) {
74+
for (const def of cmdDefs) {
7775
/* istanbul ignore next */
78-
if (seenExclusive.has(param)) {
76+
if (seenExclusive.has(def.key)) {
7977
continue
8078
}
81-
const exclusive = definitionsPool[param]?.exclusive
82-
let paramUsage = definitionsPool[param]?.usage
83-
if (exclusive) {
79+
let paramUsage = def.usage
80+
if (def.exclusive) {
8481
const exclusiveParams = [paramUsage]
85-
seenExclusive.add(param)
86-
for (const e of exclusive) {
82+
for (const e of def.exclusive) {
8783
seenExclusive.add(e)
88-
exclusiveParams.push(definitionsPool[e].usage)
84+
const eDef = cmdDefs.find(d => d.key === e) || definitions[e]
85+
exclusiveParams.push(eDef?.usage)
8986
}
9087
paramUsage = `${exclusiveParams.join('|')}`
9188
}
@@ -101,18 +98,17 @@ class BaseCommand {
10198
fullUsage.push([results, line].filter(Boolean).join('\n'))
10299

103100
// Add flag descriptions
104-
if (params.length > 0 && includeDescriptions) {
101+
if (cmdDefs.length > 0 && includeDescriptions) {
105102
fullUsage.push('')
106-
for (const param of params) {
107-
if (seenExclusive.has(param)) {
103+
for (const def of cmdDefs) {
104+
if (seenExclusive.has(def.key) && !cmdDefsKeys.has(def.key)) {
108105
continue
109106
}
110-
const def = definitionsPool[param]
111-
if (def?.description) {
107+
if (def.description) {
112108
const desc = def.description.trim().split('\n')[0]
113109
const shortcuts = def.short ? `-${def.short}|` : ''
114110
const aliases = (def.alias || []).map(v => `--${v}`).join('|')
115-
const mainFlag = `--${param}`
111+
const mainFlag = `--${def.key}`
116112
const flagName = [shortcuts, mainFlag, aliases].filter(Boolean).join('|')
117113
const requiredNote = def.required ? ' (required)' : ''
118114
fullUsage.push(` ${flagName}${requiredNote}`)
@@ -282,15 +278,15 @@ class BaseCommand {
282278
}
283279

284280
flags (depth = 1) {
285-
const commandDefinitions = this.constructor.definitions || {}
281+
const commandDefinitions = this.constructor.definitions || []
286282

287283
// Build types, shorthands, and defaults from definitions
288284
const types = {}
289285
const defaults = {}
290286
const cmdShorthands = {}
291287
const aliasMap = {} // Track which aliases map to which main keys
292288

293-
for (const def of Object.values(commandDefinitions)) {
289+
for (const def of commandDefinitions) {
294290
defaults[def.key] = def.default
295291
types[def.key] = def.type
296292

@@ -329,7 +325,7 @@ class BaseCommand {
329325
}
330326

331327
// Validate flags - only if command has definitions (new system)
332-
if (this.constructor.definitions && Object.keys(this.constructor.definitions).length > 0) {
328+
if (this.constructor.definitions && this.constructor.definitions.length > 0) {
333329
this.#validateFlags(parsed, commandDefinitions, remains)
334330
}
335331

@@ -360,7 +356,7 @@ class BaseCommand {
360356

361357
// Only include keys that are defined in commandDefinitions (main keys only)
362358
const filtered = {}
363-
for (const def of Object.values(commandDefinitions)) {
359+
for (const def of commandDefinitions) {
364360
if (def.key in parsed) {
365361
filtered[def.key] = parsed[def.key]
366362
}
@@ -373,12 +369,12 @@ class BaseCommand {
373369
// Build a set of all valid flag names (global + command-specific + shorthands)
374370
const validFlags = new Set([
375371
...Object.keys(definitions),
376-
...Object.keys(commandDefinitions),
372+
...commandDefinitions.map(d => d.key),
377373
...Object.keys(shorthands), // Add global shorthands like 'verbose', 'dd', etc.
378374
])
379375

380376
// Add aliases to valid flags
381-
for (const def of Object.values(commandDefinitions)) {
377+
for (const def of commandDefinitions) {
382378
if (def.alias && Array.isArray(def.alias)) {
383379
for (const alias of def.alias) {
384380
validFlags.add(alias)
@@ -402,7 +398,7 @@ class BaseCommand {
402398

403399
// Remove warnings for command-specific definitions that npm's global config
404400
// doesn't know about (these were queued as "unknown" during config.load())
405-
for (const def of Object.values(commandDefinitions)) {
401+
for (const def of commandDefinitions) {
406402
this.npm.config.removeWarning(def.key)
407403
if (def.alias && Array.isArray(def.alias)) {
408404
for (const alias of def.alias) {

lib/commands/completion.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class Completion extends BaseCommand {
4646
// Completion command uses args differently - they represent the command line
4747
// being completed, not actual arguments to this command, so we use an empty
4848
// definitions object to prevent flag validation
49-
static definitions = {}
49+
static definitions = []
5050

5151
// completion for the completion command
5252
static async completion (opts) {
@@ -238,7 +238,7 @@ const unescape = w => w.charAt(0) === '\'' ? w.replace(/^'|'$/g, '')
238238
// Helper to get custom definitions from a command/subcommand
239239
const getCustomDefinitions = (cmd, subCmd) => {
240240
if (!cmd) {
241-
return {}
241+
return []
242242
}
243243

244244
try {
@@ -259,14 +259,14 @@ const getCustomDefinitions = (cmd, subCmd) => {
259259
// Command not found or no definitions
260260
}
261261

262-
return {}
262+
return []
263263
}
264264

265265
// Helper to get all config names including aliases from custom definitions
266266
const getCustomConfigNames = (customDefs) => {
267267
const names = new Set()
268-
for (const [name, def] of Object.entries(customDefs)) {
269-
names.add(name)
268+
for (const def of customDefs) {
269+
names.add(def.key)
270270
if (def.alias && Array.isArray(def.alias)) {
271271
def.alias.forEach(a => names.add(a))
272272
}
@@ -312,10 +312,10 @@ const isFlag = (word, cmd, subCmd, npm) => {
312312
const customDefs = getCustomDefinitions(cmd, subCmd, npm)
313313

314314
// Check if conf is in custom definitions or is an alias
315-
let customDef = customDefs[conf]
315+
let customDef = customDefs.find(d => d.key === conf)
316316
if (!customDef) {
317317
// Check if conf is an alias for any of the custom definitions
318-
for (const [, def] of Object.entries(customDefs)) {
318+
for (const def of customDefs) {
319319
if (def.alias && Array.isArray(def.alias) && def.alias.includes(conf)) {
320320
customDef = def
321321
break

lib/commands/install.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Install extends ArboristWorkspaceCmd {
3131
'allow-git',
3232
'audit',
3333
'before',
34+
'min-release-age',
3435
'bin-links',
3536
'fund',
3637
'dry-run',

lib/commands/trust/github.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,31 @@ class TrustGitHub extends TrustCommand {
1919
'[package] --file [--repo|--repository] [--env|--environment] [-y|--yes]',
2020
]
2121

22-
static definitions = {
23-
file: new Definition('file', {
22+
static definitions = [
23+
new Definition('file', {
2424
default: null,
2525
type: String,
2626
required: true,
2727
description: 'Name of workflow file within a repositories .GitHub folder (must end in yaml, yml)',
2828
}),
29-
repository: new Definition('repository', {
29+
new Definition('repository', {
3030
default: null,
3131
type: String,
3232
description: 'Name of the repository in the format owner/repo',
3333
alias: ['repo'],
3434
}),
35-
environment: new Definition('environment', {
35+
new Definition('environment', {
3636
default: null,
3737
type: String,
3838
description: 'CI environment name',
3939
alias: ['env'],
4040
}),
4141
// globals are alphabetical
42-
'dry-run': globalDefinitions['dry-run'],
43-
json: globalDefinitions.json,
44-
registry: globalDefinitions.registry,
45-
yes: globalDefinitions.yes,
46-
}
42+
globalDefinitions['dry-run'],
43+
globalDefinitions.json,
44+
globalDefinitions.registry,
45+
globalDefinitions.yes,
46+
]
4747

4848
getEntityUrl ({ providerHostname, file, entity }) {
4949
if (file) {

lib/commands/trust/gitlab.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,30 @@ class TrustGitLab extends TrustCommand {
1919
'[package] --file [--project|--repo|--repository] [--env|--environment] [-y|--yes]',
2020
]
2121

22-
static definitions = {
23-
file: new Definition('file', {
22+
static definitions = [
23+
new Definition('file', {
2424
default: null,
2525
type: String,
2626
required: true,
2727
description: 'Name of pipeline file (e.g., .gitlab-ci.yml)',
2828
}),
29-
project: new Definition('project', {
29+
new Definition('project', {
3030
default: null,
3131
type: String,
3232
description: 'Name of the project in the format group/project or group/subgroup/project',
3333
}),
34-
environment: new Definition('environment', {
34+
new Definition('environment', {
3535
default: null,
3636
type: String,
3737
description: 'CI environment name',
3838
alias: ['env'],
3939
}),
4040
// globals are alphabetical
41-
'dry-run': globalDefinitions['dry-run'],
42-
json: globalDefinitions.json,
43-
registry: globalDefinitions.registry,
44-
yes: globalDefinitions.yes,
45-
}
41+
globalDefinitions['dry-run'],
42+
globalDefinitions.json,
43+
globalDefinitions.registry,
44+
globalDefinitions.yes,
45+
]
4646

4747
getEntityUrl ({ providerHostname, file, entity }) {
4848
if (file) {

lib/commands/trust/list.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ class TrustList extends TrustCommand {
1515
'[package]',
1616
]
1717

18-
static definitions = {
19-
json: globalDefinitions.json,
20-
registry: globalDefinitions.registry,
21-
}
18+
static definitions = [
19+
globalDefinitions.json,
20+
globalDefinitions.registry,
21+
]
2222

2323
static bodyToOptions (body) {
2424
if (body.type === 'github') {

lib/commands/trust/revoke.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ class TrustRevoke extends TrustCommand {
1414
'[package] --id=<trust-id>',
1515
]
1616

17-
static definitions = {
18-
id: new Definition('id', {
17+
static definitions = [
18+
new Definition('id', {
1919
default: null,
2020
type: String,
2121
description: 'ID of the trusted relationship to revoke',
2222
required: true,
2323
}),
24-
'dry-run': globalDefinitions['dry-run'],
25-
registry: globalDefinitions.registry,
26-
}
24+
globalDefinitions['dry-run'],
25+
globalDefinitions.registry,
26+
]
2727

2828
async exec (positionalArgs, flags) {
2929
const dryRun = this.config.get('dry-run')

tap-snapshots/test/lib/commands/install.js.test.cjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ silly logfile done cleaning log files
134134
verbose stack Error: The developer of this package has specified the following through devEngines
135135
verbose stack Invalid devEngines.runtime
136136
verbose stack Invalid name "nondescript" does not match "node" for "runtime"
137-
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:252:27)
137+
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:273:27)
138138
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:310:7)
139139
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:209:9)
140140
error code EBADDEVENGINES
@@ -199,7 +199,7 @@ warn EBADDEVENGINES }
199199
verbose stack Error: The developer of this package has specified the following through devEngines
200200
verbose stack Invalid devEngines.runtime
201201
verbose stack Invalid name "nondescript" does not match "node" for "runtime"
202-
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:252:27)
202+
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:273:27)
203203
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:310:7)
204204
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:209:9)
205205
error code EBADDEVENGINES
@@ -225,7 +225,7 @@ silly logfile done cleaning log files
225225
verbose stack Error: The developer of this package has specified the following through devEngines
226226
verbose stack Invalid devEngines.runtime
227227
verbose stack Invalid name "nondescript" does not match "node" for "runtime"
228-
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:252:27)
228+
verbose stack at Install.checkDevEngines ({CWD}/lib/base-cmd.js:273:27)
229229
verbose stack at MockNpm.execCommandClass ({CWD}/lib/npm.js:310:7)
230230
verbose stack at MockNpm.exec ({CWD}/lib/npm.js:209:9)
231231
error code EBADDEVENGINES

0 commit comments

Comments
 (0)