Skip to content

fix: make usage and completion static functions #6477

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

Merged
merged 1 commit into from
May 22, 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
110 changes: 53 additions & 57 deletions lib/base-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,65 @@

const { relative } = require('path')

const ConfigDefinitions = require('./utils/config/definitions.js')
const definitions = require('./utils/config/definitions.js')
const getWorkspaces = require('./workspaces/get-workspaces.js')

const cmdAliases = require('./utils/cmd-list').aliases
const { aliases: cmdAliases } = require('./utils/cmd-list')

class BaseCommand {
static workspaces = false
static ignoreImplicitWorkspace = true

// these are all overridden by individual commands
static name = null
static description = null
static params = null

// this is a static so that we can read from it without instantiating a command
// which would require loading the config
static get describeUsage () {
const wrapWidth = 80
const { description, usage = [''], name, params } = this

const fullUsage = [
`${description}`,
'',
'Usage:',
...usage.map(u => `npm ${name} ${u}`.trim()),
]

if (params) {
let results = ''
let line = ''
for (const param of params) {
const paramUsage = `[${definitions[param].usage}]`
if (line.length + paramUsage.length > wrapWidth) {
results = [results, line].filter(Boolean).join('\n')
line = ''
}
line = [line, paramUsage].filter(Boolean).join(' ')
}
fullUsage.push('')
fullUsage.push('Options:')
fullUsage.push([results, line].filter(Boolean).join('\n'))
}

const aliases = Object.entries(cmdAliases).reduce((p, [k, v]) => {
return p.concat(v === name ? k : [])
}, [])

if (aliases.length) {
const plural = aliases.length === 1 ? '' : 'es'
fullUsage.push('')
fullUsage.push(`alias${plural}: ${aliases.join(', ')}`)
}

fullUsage.push('')
fullUsage.push(`Run "npm help ${name}" for more info`)

return fullUsage.join('\n')
}

constructor (npm) {
this.wrapWidth = 80
this.npm = npm

const { config } = this.npm
Expand All @@ -39,59 +87,7 @@ class BaseCommand {
}

get usage () {
const usage = [
`${this.description}`,
'',
'Usage:',
]

if (!this.constructor.usage) {
usage.push(`npm ${this.name}`)
} else {
usage.push(...this.constructor.usage.map(u => `npm ${this.name} ${u}`))
}

if (this.params) {
usage.push('')
usage.push('Options:')
usage.push(this.wrappedParams)
}

const aliases = Object.keys(cmdAliases).reduce((p, c) => {
if (cmdAliases[c] === this.name) {
p.push(c)
}
return p
}, [])

if (aliases.length === 1) {
usage.push('')
usage.push(`alias: ${aliases.join(', ')}`)
} else if (aliases.length > 1) {
usage.push('')
usage.push(`aliases: ${aliases.join(', ')}`)
}

usage.push('')
usage.push(`Run "npm help ${this.name}" for more info`)

return usage.join('\n')
}

get wrappedParams () {
let results = ''
let line = ''

for (const param of this.params) {
const usage = `[${ConfigDefinitions[param].usage}]`
if (line.length && line.length + usage.length > this.wrapWidth) {
results = [results, line].filter(Boolean).join('\n')
line = ''
}
line = [line, usage].filter(Boolean).join(' ')
}
results = [results, line].filter(Boolean).join('\n')
return results
return this.constructor.describeUsage
}

usageError (prefix = '') {
Expand Down
4 changes: 2 additions & 2 deletions lib/cli-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ module.exports = async (process, validateEngines) => {

cmd = npm.argv.shift()
if (!cmd) {
npm.output(await npm.usage)
npm.output(npm.usage)
process.exitCode = 1
return exitHandler()
}
Expand All @@ -63,7 +63,7 @@ module.exports = async (process, validateEngines) => {
} catch (err) {
if (err.code === 'EUNKNOWNCOMMAND') {
const didYouMean = require('./utils/did-you-mean.js')
const suggestions = await didYouMean(npm, npm.localPrefix, cmd)
const suggestions = await didYouMean(npm.localPrefix, cmd)
npm.output(`Unknown command: "${cmd}"${suggestions}\n`)
npm.output('To see a list of supported npm commands, run:\n npm help')
process.exitCode = 1
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/access.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Access extends BaseCommand {
'revoke <scope:team> [<package>]',
]

async completion (opts) {
static async completion (opts) {
const argv = opts.conf.argv.remain
if (argv.length === 2) {
return commands
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ class Audit extends ArboristWorkspaceCmd {

static usage = ['[fix|signatures]']

async completion (opts) {
static async completion (opts) {
const argv = opts.conf.argv.remain

if (argv.length === 2) {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class Cache extends BaseCommand {
'verify',
]

async completion (opts) {
static async completion (opts) {
const argv = opts.conf.argv.remain
if (argv.length === 2) {
return ['add', 'clean', 'verify', 'ls']
Expand Down
19 changes: 12 additions & 7 deletions lib/commands/completion.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ const fs = require('fs/promises')
const nopt = require('nopt')
const { resolve } = require('path')

const Npm = require('../npm.js')
const { definitions, shorthands } = require('../utils/config/index.js')
const { commands, aliases } = require('../utils/cmd-list.js')
const { commands, aliases, deref } = require('../utils/cmd-list.js')
const configNames = Object.keys(definitions)
const shorthandNames = Object.keys(shorthands)
const allConfs = configNames.concat(shorthandNames)
Expand All @@ -48,7 +49,7 @@ class Completion extends BaseCommand {
static name = 'completion'

// completion for the completion command
async completion (opts) {
static async completion (opts) {
if (opts.w > 2) {
return
}
Expand Down Expand Up @@ -156,10 +157,14 @@ class Completion extends BaseCommand {
// at this point, if words[1] is some kind of npm command,
// then complete on it.
// otherwise, do nothing
const impl = await this.npm.cmd(cmd)
if (impl.completion) {
const comps = await impl.completion(opts)
return this.wrap(opts, comps)
try {
const { completion } = Npm.cmd(cmd)
if (completion) {
const comps = await completion(opts, this.npm)
return this.wrap(opts, comps)
}
} catch {
// it wasnt a valid command, so do nothing
}
}

Expand Down Expand Up @@ -267,7 +272,7 @@ const cmdCompl = (opts, npm) => {
return matches
}

const derefs = new Set([...matches.map(c => npm.deref(c))])
const derefs = new Set([...matches.map(c => deref(c))])
if (derefs.size === 1) {
return [...derefs]
}
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class Config extends BaseCommand {

static skipConfigValidation = true

async completion (opts) {
static async completion (opts) {
const argv = opts.conf.argv.remain
if (argv[1] !== 'config') {
argv.unshift('config')
Expand Down
6 changes: 3 additions & 3 deletions lib/commands/deprecate.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ class Deprecate extends BaseCommand {

static ignoreImplicitWorkspace = false

async completion (opts) {
static async completion (opts, npm) {
if (opts.conf.argv.remain.length > 1) {
return []
}

const username = await getIdentity(this.npm, this.npm.flatOptions)
const packages = await libaccess.getPackages(username, this.npm.flatOptions)
const username = await getIdentity(npm, npm.flatOptions)
const packages = await libaccess.getPackages(username, npm.flatOptions)
return Object.keys(packages)
.filter((name) =>
packages[name] === 'write' &&
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/dist-tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DistTag extends BaseCommand {
static workspaces = true
static ignoreImplicitWorkspace = false

async completion (opts) {
static async completion (opts) {
const argv = opts.conf.argv.remain
if (argv.length === 2) {
return ['add', 'rm', 'ls']
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class Edit extends BaseCommand {

// TODO
/* istanbul ignore next */
async completion (opts) {
return completion(this.npm, opts)
static async completion (opts, npm) {
return completion(npm, opts)
}

async exec (args) {
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/explain.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class Explain extends ArboristWorkspaceCmd {

// TODO
/* istanbul ignore next */
async completion (opts) {
static async completion (opts, npm) {
const completion = require('../utils/completion/installed-deep.js')
return completion(this.npm, opts)
return completion(npm, opts)
}

async exec (args) {
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/explore.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class Explore extends BaseCommand {

// TODO
/* istanbul ignore next */
async completion (opts) {
return completion(this.npm, opts)
static async completion (opts, npm) {
return completion(npm, opts)
}

async exec (args) {
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/fund.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ class Fund extends ArboristWorkspaceCmd {

// TODO
/* istanbul ignore next */
async completion (opts) {
static async completion (opts, npm) {
const completion = require('../utils/completion/installed-deep.js')
return completion(this.npm, opts)
return completion(npm, opts)
}

async exec (args) {
Expand Down
7 changes: 4 additions & 3 deletions lib/commands/get.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const Npm = require('../npm.js')
const BaseCommand = require('../base-command.js')

class Get extends BaseCommand {
Expand All @@ -9,9 +10,9 @@ class Get extends BaseCommand {

// TODO
/* istanbul ignore next */
async completion (opts) {
const config = await this.npm.cmd('config')
return config.completion(opts)
static async completion (opts) {
const Config = Npm.cmd('config')
return Config.completion(opts)
}

async exec (args) {
Expand Down
9 changes: 5 additions & 4 deletions lib/commands/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require('path')
const openUrl = require('../utils/open-url.js')
const { glob } = require('glob')
const localeCompare = require('@isaacs/string-locale-compare')('en')
const { deref } = require('../utils/cmd-list.js')

const globify = pattern => pattern.split('\\').join('/')
const BaseCommand = require('../base-command.js')
Expand All @@ -26,11 +27,11 @@ class Help extends BaseCommand {
static usage = ['<term> [<terms..>]']
static params = ['viewer']

async completion (opts) {
static async completion (opts, npm) {
if (opts.conf.argv.remain.length > 2) {
return []
}
const g = path.resolve(this.npm.npmRoot, 'man/man[0-9]/*.[0-9]')
const g = path.resolve(npm.npmRoot, 'man/man[0-9]/*.[0-9]')
let files = await glob(globify(g))
// preserve glob@8 behavior
files = files.sort((a, b) => a.localeCompare(b, 'en'))
Expand All @@ -49,7 +50,7 @@ class Help extends BaseCommand {
const manSearch = /^\d+$/.test(args[0]) ? `man${args.shift()}` : 'man*'

if (!args.length) {
return this.npm.output(await this.npm.usage)
return this.npm.output(this.npm.usage)
}

// npm help foo bar baz: search topics
Expand All @@ -58,7 +59,7 @@ class Help extends BaseCommand {
}

// `npm help package.json`
const arg = (this.npm.deref(args[0]) || args[0]).replace('.json', '-json')
const arg = (deref(args[0]) || args[0]).replace('.json', '-json')

// find either section.n or npm-section.n
const f = globify(path.resolve(this.npm.npmRoot, `man/${manSearch}/?(npm-)${arg}.[0-9]*`))
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Install extends ArboristWorkspaceCmd {

static usage = ['[<package-spec> ...]']

async completion (opts) {
static async completion (opts) {
const { partialWord } = opts
// install can complete to a folder with a package.json, or any package.
// if it has a slash, then it's gotta be a folder
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ class Link extends ArboristWorkspaceCmd {
...super.params,
]

async completion (opts) {
const dir = this.npm.globalDir
static async completion (opts, npm) {
const dir = npm.globalDir
const files = await readdir(dir)
return files.filter(f => !/^[._-]/.test(f))
}
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ class LS extends ArboristWorkspaceCmd {

// TODO
/* istanbul ignore next */
async completion (opts) {
static async completion (opts, npm) {
const completion = require('../utils/completion/installed-deep.js')
return completion(this.npm, opts)
return completion(npm, opts)
}

async exec (args) {
Expand Down
Loading