Skip to content

Commit

Permalink
feat(workspaces): add repo and docs
Browse files Browse the repository at this point in the history
This adds workspaces support to `npm repo` and `npm docs`.

It also updates the usage output to support the -w and -ws parameters
output, and cleans up some unneccessary functions in `run-script` and
`exec`.

PR-URL: #2972
Credit: @wraithgar
Close: #2972
Reviewed-by: @nlf
  • Loading branch information
wraithgar committed Mar 31, 2021
1 parent 1267a41 commit 8bcc5d7
Show file tree
Hide file tree
Showing 13 changed files with 454 additions and 181 deletions.
1 change: 1 addition & 0 deletions lib/base-command.js
Expand Up @@ -27,6 +27,7 @@ class BaseCommand {
usage = `${usage}${this.constructor.usage.map(u => `npm ${this.constructor.name} ${u}`).join('\n')}`

if (this.constructor.params)
// TODO word wrap this along params boundaries
usage = `${usage}\n\nOptions:\n[${this.constructor.params.map(p => ConfigDefinitions[p].usage).join('] [')}]`

// Mostly this just appends aliases, this could be more clear
Expand Down
16 changes: 16 additions & 0 deletions lib/docs.js
Expand Up @@ -2,6 +2,7 @@ const log = require('npmlog')
const pacote = require('pacote')
const openUrl = require('./utils/open-url.js')
const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js')
const getWorkspaces = require('./workspaces/get-workspaces.js')

const BaseCommand = require('./base-command.js')
class Docs extends BaseCommand {
Expand All @@ -15,6 +16,11 @@ class Docs extends BaseCommand {
return 'docs'
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get params () {
return ['browser', 'workspace', 'workspaces']
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get usage () {
return ['[<pkgname> [<pkgname> ...]]']
Expand All @@ -24,13 +30,23 @@ class Docs extends BaseCommand {
this.docs(args).then(() => cb()).catch(cb)
}

execWorkspaces (args, filters, cb) {
this.docsWorkspaces(args, filters).then(() => cb()).catch(cb)
}

async docs (args) {
if (!args || !args.length)
args = ['.']

await Promise.all(args.map(pkg => this.getDocs(pkg)))
}

async docsWorkspaces (args, filters) {
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })
return this.docs([...workspaces.values()])
}

async getDocs (pkg) {
const opts = { ...this.npm.flatOptions, fullMetadata: true }
const mani = await pacote.manifest(pkg, opts)
Expand Down
12 changes: 7 additions & 5 deletions lib/exec.js
Expand Up @@ -53,6 +53,11 @@ class Exec extends BaseCommand {
return 'Run a command from a local or remote npm package'
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get params () {
return ['workspace', 'workspaces']
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get name () {
return 'exec'
Expand Down Expand Up @@ -339,12 +344,9 @@ class Exec extends BaseCommand {
.slice(0, 16)
}

async workspaces (filters) {
return getWorkspaces(filters, { path: this.npm.localPrefix })
}

async _execWorkspaces (args, filters) {
const workspaces = await this.workspaces(filters)
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })
const getLocationMsg = async path => {
const color = this.npm.config.get('color')
const colorize = color ? chalk : nocolor
Expand Down
16 changes: 16 additions & 0 deletions lib/repo.js
@@ -1,5 +1,6 @@
const log = require('npmlog')
const pacote = require('pacote')
const getWorkspaces = require('./workspaces/get-workspaces.js')
const { URL } = require('url')

const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js')
Expand All @@ -17,6 +18,11 @@ class Repo extends BaseCommand {
return 'repo'
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get params () {
return ['browser', 'workspace', 'workspaces']
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get usage () {
return ['[<pkgname> [<pkgname> ...]]']
Expand All @@ -26,13 +32,23 @@ class Repo extends BaseCommand {
this.repo(args).then(() => cb()).catch(cb)
}

execWorkspaces (args, filters, cb) {
this.repoWorkspaces(args, filters).then(() => cb()).catch(cb)
}

async repo (args) {
if (!args || !args.length)
args = ['.']

await Promise.all(args.map(pkg => this.get(pkg)))
}

async repoWorkspaces (args, filters) {
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })
return this.repo([...workspaces.values()])
}

async get (pkg) {
const opts = { ...this.npm.flatOptions, fullMetadata: true }
const mani = await pacote.manifest(pkg, opts)
Expand Down
15 changes: 9 additions & 6 deletions lib/run-script.js
Expand Up @@ -34,6 +34,11 @@ class RunScript extends BaseCommand {
return 'Run arbitrary package scripts'
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get params () {
return ['workspace', 'workspaces']
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get name () {
return 'run-script'
Expand Down Expand Up @@ -182,13 +187,10 @@ class RunScript extends BaseCommand {
return allScripts
}

async workspaces (filters) {
return getWorkspaces(filters, { path: this.npm.localPrefix })
}

async runWorkspaces (args, filters) {
const res = []
const workspaces = await this.workspaces(filters)
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })

for (const workspacePath of workspaces.values()) {
const pkg = await rpj(`${workspacePath}/package.json`)
Expand Down Expand Up @@ -219,7 +221,8 @@ class RunScript extends BaseCommand {
}

async listWorkspaces (args, filters) {
const workspaces = await this.workspaces(filters)
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })

if (log.level === 'silent')
return
Expand Down
48 changes: 33 additions & 15 deletions lib/utils/config/definition.js
Expand Up @@ -45,6 +45,7 @@ class Definition {
this.defaultDescription = describeValue(this.default)
if (!this.typeDescription)
this.typeDescription = describeType(this.type)
// hint is only used for non-boolean values
if (!this.hint)
this.hint = `<${this.key}>`
if (!this.usage)
Expand Down Expand Up @@ -79,26 +80,43 @@ ${description}
}
}

// Usage for a single param, abstracted because we have arrays of types in
// config definition
const paramUsage = (type, def) => {
const describeUsage = (def) => {
let key = `--${def.key}`
if (def.short && typeof def.short === 'string')
key = `-${def.short}|${key}`
if (type === Boolean)
return `${key}`
else
return `${key} ${def.hint}`
}

const describeUsage = (def) => {
if (Array.isArray(def.type)) {
if (!def.type.some(d => d !== null && typeof d !== 'string'))
return `--${def.key} <${def.type.filter(d => d).join('|')}>`
else
return def.type.filter(d => d).map((t) => paramUsage(t, def)).join('|')
// Single type
if (!Array.isArray(def.type))
return `${key}${def.type === Boolean ? '' : ' ' + def.hint}`

// Multiple types
let types = def.type
const multiple = types.includes(Array)
const bool = types.includes(Boolean)

// null type means optional and doesn't currently affect usage output since
// all non-optional params have defaults so we render everything as optional
types = types.filter(t => t !== null && t !== Array && t !== Boolean)

if (!types.length)
return key

let description
if (!types.some(t => typeof t !== 'string'))
// Specific values, use specifics given
description = `<${types.filter(d => d).join('|')}>`
else {
// Generic values, use hint
description = def.hint
}
return paramUsage(def.type, def)

if (multiple)
description = `${description} [${description} ...]`

if (bool)
key = `${key}|${key}`

return `${key} ${description}`
}

const describeType = type => {
Expand Down
3 changes: 2 additions & 1 deletion lib/utils/config/definitions.js
Expand Up @@ -223,7 +223,7 @@ define('audit', {

define('audit-level', {
default: null,
type: ['info', 'low', 'moderate', 'high', 'critical', 'none', null],
type: [null, 'info', 'low', 'moderate', 'high', 'critical', 'none'],
description: `
The minimum level of vulnerability for \`npm audit\` to exit with
a non-zero exit code.
Expand Down Expand Up @@ -2042,6 +2042,7 @@ define('which', {
define('workspace', {
default: [],
type: [String, Array],
hint: '<workspace-name>',
short: 'w',
description: `
Enable running a command in the context of the configured workspaces of the
Expand Down
Expand Up @@ -64,7 +64,7 @@ registry and all registries configured for scopes. See the documentation for
#### \`audit-level\`
* Default: null
* Type: "info", "low", "moderate", "high", "critical", "none", or null
* Type: null, "info", "low", "moderate", "high", "critical", or "none"
The minimum level of vulnerability for \`npm audit\` to exit with a non-zero
exit code.
Expand Down
12 changes: 12 additions & 0 deletions tap-snapshots/test-lib-utils-npm-usage.js-TAP.test.js
Expand Up @@ -334,6 +334,9 @@ All commands:
Usage:
npm docs [<pkgname> [<pkgname> ...]]
Options:
[--browser|--browser <browser>] [-w|--workspace <workspace-name> [<workspace-name> ...]] [-ws|--workspaces]
alias: home
Run "npm help docs" for more info
Expand Down Expand Up @@ -366,6 +369,9 @@ All commands:
npm exec -c '<cmd> [args...]'
npm exec --package=foo -c '<cmd> [args...]'
Options:
[-w|--workspace <workspace-name> [<workspace-name> ...]] [-ws|--workspaces]
alias: x
Run "npm help exec" for more info
Expand Down Expand Up @@ -695,6 +701,9 @@ All commands:
Usage:
npm repo [<pkgname> [<pkgname> ...]]
Options:
[--browser|--browser <browser>] [-w|--workspace <workspace-name> [<workspace-name> ...]] [-ws|--workspaces]
Run "npm help repo" for more info
restart npm restart
Expand Down Expand Up @@ -725,6 +734,9 @@ All commands:
Usage:
npm run-script <command> [-- <args>]
Options:
[-w|--workspace <workspace-name> [<workspace-name> ...]] [-ws|--workspaces]
aliases: run, rum, urn
Run "npm help run-script" for more info
Expand Down

0 comments on commit 8bcc5d7

Please sign in to comment.