Skip to content

Commit

Permalink
feat(outdated): add workspaces support
Browse files Browse the repository at this point in the history
- Add listing outdated direct deps of a workspace in `npm outdated`
- Add ability to filter the results of `npm outdated` using `-w` config
- Added tests and docs

Fixes: npm/statusboard#303

PR-URL: #3260
Credit: @ruyadorno
Close: #3260
Reviewed-by: @isaacs
  • Loading branch information
ruyadorno authored and isaacs committed May 20, 2021
1 parent fde3546 commit 83df366
Show file tree
Hide file tree
Showing 6 changed files with 428 additions and 10 deletions.
26 changes: 25 additions & 1 deletion docs/content/commands/npm-outdated.md
Expand Up @@ -15,7 +15,8 @@ npm outdated [[<@scope>/]<pkg> ...]
This command will check the registry to see if any (or, specific) installed
packages are currently outdated.

By default, only the direct dependencies of the root project are shown.
By default, only the direct dependencies of the root project and direct
dependencies of your configured *workspaces* are shown.
Use `--all` to find all outdated meta-dependencies as well.

In the output:
Expand Down Expand Up @@ -134,6 +135,28 @@ folder instead of the current working directory. See
* bin files are linked to `{prefix}/bin`
* man pages are linked to `{prefix}/share/man`

#### `workspace`

* Default:
* Type: String (can be set multiple times)

Enable running a command in the context of the configured workspaces of the
current project while filtering by running only the workspaces defined by
this configuration option.

Valid values for the `workspace` config are either:

* Workspace names
* Path to a workspace directory
* Path to a parent workspace directory (will result to selecting all of the
nested workspaces)

When set for the `npm init` command, this may be set to the folder of a
workspace which does not yet exist, to create the folder and set it up as a
brand new workspace within the project.

This value is not exported to the environment for child processes.

<!-- AUTOGENERATED CONFIG DESCRIPTIONS END -->

### See Also
Expand All @@ -142,3 +165,4 @@ folder instead of the current working directory. See
* [npm dist-tag](/commands/npm-dist-tag)
* [npm registry](/using-npm/registry)
* [npm folders](/configuring-npm/folders)
* [npm workspaces](/using-npm/workspaces)
78 changes: 69 additions & 9 deletions lib/outdated.js
Expand Up @@ -2,17 +2,17 @@ const os = require('os')
const path = require('path')
const pacote = require('pacote')
const table = require('text-table')
const color = require('ansicolors')
const color = require('chalk')
const styles = require('ansistyles')
const npa = require('npm-package-arg')
const pickManifest = require('npm-pick-manifest')

const Arborist = require('@npmcli/arborist')

const ansiTrim = require('./utils/ansi-trim.js')
const BaseCommand = require('./base-command.js')
const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js')

class Outdated extends BaseCommand {
class Outdated extends ArboristWorkspaceCmd {
/* istanbul ignore next - see test/lib/load-all-commands.js */
static get description () {
return 'Check for outdated packages'
Expand All @@ -36,6 +36,7 @@ class Outdated extends BaseCommand {
'long',
'parseable',
'global',
'workspace',
]
}

Expand All @@ -58,6 +59,9 @@ class Outdated extends BaseCommand {
this.list = []
this.tree = await arb.loadActual()

if (this.workspaces && this.workspaces.length)
this.filterSet = arb.workspaceDependencySet(this.tree, this.workspaces)

if (args.length !== 0) {
// specific deps
for (let i = 0; i < args.length; i++) {
Expand Down Expand Up @@ -116,8 +120,14 @@ class Outdated extends BaseCommand {
}

getEdges (nodes, type) {
if (!nodes)
return this.getEdgesOut(this.tree)
// when no nodes are provided then it should only read direct deps
// from the root node and its workspaces direct dependencies
if (!nodes) {
this.getEdgesOut(this.tree)
this.getWorkspacesEdges()
return
}

for (const node of nodes) {
type === 'edgesOut'
? this.getEdgesOut(node)
Expand All @@ -127,16 +137,45 @@ class Outdated extends BaseCommand {

getEdgesIn (node) {
for (const edge of node.edgesIn)
this.edges.add(edge)
this.trackEdge(edge)
}

getEdgesOut (node) {
// TODO: normalize usage of edges and avoid looping through nodes here
if (this.npm.config.get('global')) {
for (const child of node.children.values())
this.edges.add(child)
this.trackEdge(child)
} else {
for (const edge of node.edgesOut.values())
this.edges.add(edge)
this.trackEdge(edge)
}
}

trackEdge (edge) {
const filteredOut =
edge.from
&& this.filterSet
&& this.filterSet.size > 0
&& !this.filterSet.has(edge.from.target || edge.from)

if (filteredOut)
return

this.edges.add(edge)
}

getWorkspacesEdges (node) {
if (this.npm.config.get('global'))
return

for (const edge of this.tree.edgesOut.values()) {
const workspace = edge
&& edge.to
&& edge.to.target
&& edge.to.target.isWorkspace

if (workspace)
this.getEdgesOut(edge.to.target)
}
}

Expand Down Expand Up @@ -188,6 +227,10 @@ class Outdated extends BaseCommand {
current !== wanted.version ||
wanted.version !== latest.version
) {
const dependent = edge.from ?
this.maybeWorkspaceName(edge.from)
: 'global'

this.list.push({
name: edge.name,
path,
Expand All @@ -196,7 +239,7 @@ class Outdated extends BaseCommand {
location,
wanted: wanted.version,
latest: latest.version,
dependent: edge.from ? edge.from.name : 'global',
dependent,
homepage: packument.homepage,
})
}
Expand All @@ -212,6 +255,23 @@ class Outdated extends BaseCommand {
}
}

maybeWorkspaceName (node) {
if (!node.isWorkspace)
return node.name

const humanOutput =
!this.npm.config.get('json') && !this.npm.config.get('parseable')

const workspaceName =
humanOutput
? node.pkgid
: node.name

return this.npm.color && humanOutput
? color.green(workspaceName)
: workspaceName
}

// formatting functions
makePretty (dep) {
const {
Expand Down
1 change: 1 addition & 0 deletions tap-snapshots/test/lib/load-all-commands.js.test.cjs
Expand Up @@ -621,6 +621,7 @@ npm outdated [[<@scope>/]<pkg> ...]
Options:
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
Run "npm help outdated" for more info
`
Expand Down
98 changes: 98 additions & 0 deletions tap-snapshots/test/lib/outdated.js.test.cjs
Expand Up @@ -152,3 +152,101 @@ exports[`test/lib/outdated.js TAP should display outdated deps outdated specific
Package Current Wanted Latest Location Depended by
cat 1.0.0 1.0.1 1.0.1 node_modules/cat tap-testdir-outdated-should-display-outdated-deps
`

exports[`test/lib/outdated.js TAP workspaces > should display all dependencies 1`] = `
Package Current Wanted Latest Location Depended by
cat 1.0.0 1.0.1 1.0.1 node_modules/cat a@1.0.0
chai 1.0.0 1.0.1 1.0.1 node_modules/chai foo
dog 1.0.1 1.0.1 2.0.0 node_modules/dog tap-testdir-outdated-workspaces
theta MISSING 1.0.1 1.0.1 - c@1.0.0
`

exports[`test/lib/outdated.js TAP workspaces > should display json results filtered by ws 1`] = `
{
"cat": {
"current": "1.0.0",
"wanted": "1.0.1",
"latest": "1.0.1",
"dependent": "a",
"location": "{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat"
}
}
`

exports[`test/lib/outdated.js TAP workspaces > should display missing deps when filtering by ws 1`] = `
Package Current Wanted Latest Location Depended by
theta MISSING 1.0.1 1.0.1 - c@1.0.0
`

exports[`test/lib/outdated.js TAP workspaces > should display nested deps when filtering by ws and using --all 1`] = `
Package Current Wanted Latest Location Depended by
cat 1.0.0 1.0.1 1.0.1 node_modules/cat a@1.0.0
chai 1.0.0 1.0.1 1.0.1 node_modules/chai foo
`

exports[`test/lib/outdated.js TAP workspaces > should display no results if ws has no deps to display 1`] = `
`

exports[`test/lib/outdated.js TAP workspaces > should display parseable results filtered by ws 1`] = `
{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat:cat@1.0.1:cat@1.0.0:cat@1.0.1:a
`

exports[`test/lib/outdated.js TAP workspaces > should display results filtered by ws 1`] = `
Package Current Wanted Latest Location Depended by
cat 1.0.0 1.0.1 1.0.1 node_modules/cat a@1.0.0
`

exports[`test/lib/outdated.js TAP workspaces > should display ws outdated deps human output 1`] = `
Package Current Wanted Latest Location Depended by
cat 1.0.0 1.0.1 1.0.1 node_modules/cat a@1.0.0
dog 1.0.1 1.0.1 2.0.0 node_modules/dog tap-testdir-outdated-workspaces
theta MISSING 1.0.1 1.0.1 - c@1.0.0
`

exports[`test/lib/outdated.js TAP workspaces > should display ws outdated deps json output 1`] = `
{
"cat": {
"current": "1.0.0",
"wanted": "1.0.1",
"latest": "1.0.1",
"dependent": "a",
"location": "{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat"
},
"dog": {
"current": "1.0.1",
"wanted": "1.0.1",
"latest": "2.0.0",
"dependent": "tap-testdir-outdated-workspaces",
"location": "{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/dog"
},
"theta": {
"wanted": "1.0.1",
"latest": "1.0.1",
"dependent": "c"
}
}
`

exports[`test/lib/outdated.js TAP workspaces > should display ws outdated deps parseable output 1`] = `
{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat:cat@1.0.1:cat@1.0.0:cat@1.0.1:a
{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/dog:dog@1.0.1:dog@1.0.1:dog@2.0.0:tap-testdir-outdated-workspaces
:theta@1.0.1:MISSING:theta@1.0.1:c
`

exports[`test/lib/outdated.js TAP workspaces > should highlight ws in dependend by section 1`] = `
Package Current Wanted Latest Location Depended by
cat 1.0.0 1.0.1 1.0.1 node_modules/cat a@1.0.0
dog 1.0.1 1.0.1 2.0.0 node_modules/dog tap-testdir-outdated-workspaces
theta MISSING 1.0.1 1.0.1 - c@1.0.0
`
1 change: 1 addition & 0 deletions tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
Expand Up @@ -712,6 +712,7 @@ All commands:
Options:
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
Run "npm help outdated" for more info
Expand Down

0 comments on commit 83df366

Please sign in to comment.