Skip to content

Commit 83df366

Browse files
ruyadornoisaacs
authored andcommitted
feat(outdated): add workspaces support
- 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
1 parent fde3546 commit 83df366

File tree

6 files changed

+428
-10
lines changed

6 files changed

+428
-10
lines changed

docs/content/commands/npm-outdated.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ npm outdated [[<@scope>/]<pkg> ...]
1515
This command will check the registry to see if any (or, specific) installed
1616
packages are currently outdated.
1717

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

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

138+
#### `workspace`
139+
140+
* Default:
141+
* Type: String (can be set multiple times)
142+
143+
Enable running a command in the context of the configured workspaces of the
144+
current project while filtering by running only the workspaces defined by
145+
this configuration option.
146+
147+
Valid values for the `workspace` config are either:
148+
149+
* Workspace names
150+
* Path to a workspace directory
151+
* Path to a parent workspace directory (will result to selecting all of the
152+
nested workspaces)
153+
154+
When set for the `npm init` command, this may be set to the folder of a
155+
workspace which does not yet exist, to create the folder and set it up as a
156+
brand new workspace within the project.
157+
158+
This value is not exported to the environment for child processes.
159+
137160
<!-- AUTOGENERATED CONFIG DESCRIPTIONS END -->
138161

139162
### See Also
@@ -142,3 +165,4 @@ folder instead of the current working directory. See
142165
* [npm dist-tag](/commands/npm-dist-tag)
143166
* [npm registry](/using-npm/registry)
144167
* [npm folders](/configuring-npm/folders)
168+
* [npm workspaces](/using-npm/workspaces)

lib/outdated.js

+69-9
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ const os = require('os')
22
const path = require('path')
33
const pacote = require('pacote')
44
const table = require('text-table')
5-
const color = require('ansicolors')
5+
const color = require('chalk')
66
const styles = require('ansistyles')
77
const npa = require('npm-package-arg')
88
const pickManifest = require('npm-pick-manifest')
99

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

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

15-
class Outdated extends BaseCommand {
15+
class Outdated extends ArboristWorkspaceCmd {
1616
/* istanbul ignore next - see test/lib/load-all-commands.js */
1717
static get description () {
1818
return 'Check for outdated packages'
@@ -36,6 +36,7 @@ class Outdated extends BaseCommand {
3636
'long',
3737
'parseable',
3838
'global',
39+
'workspace',
3940
]
4041
}
4142

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

62+
if (this.workspaces && this.workspaces.length)
63+
this.filterSet = arb.workspaceDependencySet(this.tree, this.workspaces)
64+
6165
if (args.length !== 0) {
6266
// specific deps
6367
for (let i = 0; i < args.length; i++) {
@@ -116,8 +120,14 @@ class Outdated extends BaseCommand {
116120
}
117121

118122
getEdges (nodes, type) {
119-
if (!nodes)
120-
return this.getEdgesOut(this.tree)
123+
// when no nodes are provided then it should only read direct deps
124+
// from the root node and its workspaces direct dependencies
125+
if (!nodes) {
126+
this.getEdgesOut(this.tree)
127+
this.getWorkspacesEdges()
128+
return
129+
}
130+
121131
for (const node of nodes) {
122132
type === 'edgesOut'
123133
? this.getEdgesOut(node)
@@ -127,16 +137,45 @@ class Outdated extends BaseCommand {
127137

128138
getEdgesIn (node) {
129139
for (const edge of node.edgesIn)
130-
this.edges.add(edge)
140+
this.trackEdge(edge)
131141
}
132142

133143
getEdgesOut (node) {
144+
// TODO: normalize usage of edges and avoid looping through nodes here
134145
if (this.npm.config.get('global')) {
135146
for (const child of node.children.values())
136-
this.edges.add(child)
147+
this.trackEdge(child)
137148
} else {
138149
for (const edge of node.edgesOut.values())
139-
this.edges.add(edge)
150+
this.trackEdge(edge)
151+
}
152+
}
153+
154+
trackEdge (edge) {
155+
const filteredOut =
156+
edge.from
157+
&& this.filterSet
158+
&& this.filterSet.size > 0
159+
&& !this.filterSet.has(edge.from.target || edge.from)
160+
161+
if (filteredOut)
162+
return
163+
164+
this.edges.add(edge)
165+
}
166+
167+
getWorkspacesEdges (node) {
168+
if (this.npm.config.get('global'))
169+
return
170+
171+
for (const edge of this.tree.edgesOut.values()) {
172+
const workspace = edge
173+
&& edge.to
174+
&& edge.to.target
175+
&& edge.to.target.isWorkspace
176+
177+
if (workspace)
178+
this.getEdgesOut(edge.to.target)
140179
}
141180
}
142181

@@ -188,6 +227,10 @@ class Outdated extends BaseCommand {
188227
current !== wanted.version ||
189228
wanted.version !== latest.version
190229
) {
230+
const dependent = edge.from ?
231+
this.maybeWorkspaceName(edge.from)
232+
: 'global'
233+
191234
this.list.push({
192235
name: edge.name,
193236
path,
@@ -196,7 +239,7 @@ class Outdated extends BaseCommand {
196239
location,
197240
wanted: wanted.version,
198241
latest: latest.version,
199-
dependent: edge.from ? edge.from.name : 'global',
242+
dependent,
200243
homepage: packument.homepage,
201244
})
202245
}
@@ -212,6 +255,23 @@ class Outdated extends BaseCommand {
212255
}
213256
}
214257

258+
maybeWorkspaceName (node) {
259+
if (!node.isWorkspace)
260+
return node.name
261+
262+
const humanOutput =
263+
!this.npm.config.get('json') && !this.npm.config.get('parseable')
264+
265+
const workspaceName =
266+
humanOutput
267+
? node.pkgid
268+
: node.name
269+
270+
return this.npm.color && humanOutput
271+
? color.green(workspaceName)
272+
: workspaceName
273+
}
274+
215275
// formatting functions
216276
makePretty (dep) {
217277
const {

tap-snapshots/test/lib/load-all-commands.js.test.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ npm outdated [[<@scope>/]<pkg> ...]
621621
622622
Options:
623623
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global]
624+
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
624625
625626
Run "npm help outdated" for more info
626627
`

tap-snapshots/test/lib/outdated.js.test.cjs

+98
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,101 @@ exports[`test/lib/outdated.js TAP should display outdated deps outdated specific
152152
Package Current Wanted Latest Location Depended by
153153
cat 1.0.0 1.0.1 1.0.1 node_modules/cat tap-testdir-outdated-should-display-outdated-deps
154154
`
155+
156+
exports[`test/lib/outdated.js TAP workspaces > should display all dependencies 1`] = `
157+
158+
Package Current Wanted Latest Location Depended by
159+
cat 1.0.0 1.0.1 1.0.1 node_modules/cat [email protected]
160+
chai 1.0.0 1.0.1 1.0.1 node_modules/chai foo
161+
dog 1.0.1 1.0.1 2.0.0 node_modules/dog tap-testdir-outdated-workspaces
162+
theta MISSING 1.0.1 1.0.1 - [email protected]
163+
`
164+
165+
exports[`test/lib/outdated.js TAP workspaces > should display json results filtered by ws 1`] = `
166+
167+
{
168+
"cat": {
169+
"current": "1.0.0",
170+
"wanted": "1.0.1",
171+
"latest": "1.0.1",
172+
"dependent": "a",
173+
"location": "{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat"
174+
}
175+
}
176+
`
177+
178+
exports[`test/lib/outdated.js TAP workspaces > should display missing deps when filtering by ws 1`] = `
179+
180+
Package Current Wanted Latest Location Depended by
181+
theta MISSING 1.0.1 1.0.1 - [email protected]
182+
`
183+
184+
exports[`test/lib/outdated.js TAP workspaces > should display nested deps when filtering by ws and using --all 1`] = `
185+
186+
Package Current Wanted Latest Location Depended by
187+
cat 1.0.0 1.0.1 1.0.1 node_modules/cat [email protected]
188+
chai 1.0.0 1.0.1 1.0.1 node_modules/chai foo
189+
`
190+
191+
exports[`test/lib/outdated.js TAP workspaces > should display no results if ws has no deps to display 1`] = `
192+
193+
`
194+
195+
exports[`test/lib/outdated.js TAP workspaces > should display parseable results filtered by ws 1`] = `
196+
197+
{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat:[email protected]:[email protected]:[email protected]:a
198+
`
199+
200+
exports[`test/lib/outdated.js TAP workspaces > should display results filtered by ws 1`] = `
201+
202+
Package Current Wanted Latest Location Depended by
203+
cat 1.0.0 1.0.1 1.0.1 node_modules/cat [email protected]
204+
`
205+
206+
exports[`test/lib/outdated.js TAP workspaces > should display ws outdated deps human output 1`] = `
207+
208+
Package Current Wanted Latest Location Depended by
209+
cat 1.0.0 1.0.1 1.0.1 node_modules/cat [email protected]
210+
dog 1.0.1 1.0.1 2.0.0 node_modules/dog tap-testdir-outdated-workspaces
211+
theta MISSING 1.0.1 1.0.1 - [email protected]
212+
`
213+
214+
exports[`test/lib/outdated.js TAP workspaces > should display ws outdated deps json output 1`] = `
215+
216+
{
217+
"cat": {
218+
"current": "1.0.0",
219+
"wanted": "1.0.1",
220+
"latest": "1.0.1",
221+
"dependent": "a",
222+
"location": "{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat"
223+
},
224+
"dog": {
225+
"current": "1.0.1",
226+
"wanted": "1.0.1",
227+
"latest": "2.0.0",
228+
"dependent": "tap-testdir-outdated-workspaces",
229+
"location": "{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/dog"
230+
},
231+
"theta": {
232+
"wanted": "1.0.1",
233+
"latest": "1.0.1",
234+
"dependent": "c"
235+
}
236+
}
237+
`
238+
239+
exports[`test/lib/outdated.js TAP workspaces > should display ws outdated deps parseable output 1`] = `
240+
241+
{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/cat:[email protected]:[email protected]:[email protected]:a
242+
{CWD}/test/lib/tap-testdir-outdated-workspaces/node_modules/dog:[email protected]:[email protected]:[email protected]:tap-testdir-outdated-workspaces
243+
244+
`
245+
246+
exports[`test/lib/outdated.js TAP workspaces > should highlight ws in dependend by section 1`] = `
247+
248+
Package Current Wanted Latest Location Depended by
249+
cat 1.0.0 1.0.1 1.0.1 node_modules/cat [[email protected]
250+
dog 1.0.1 1.0.1 2.0.0 node_modules/dog tap-testdir-outdated-workspaces
251+
theta MISSING 1.0.1 1.0.1 - [[email protected]
252+
`

tap-snapshots/test/lib/utils/npm-usage.js.test.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@ All commands:
712712
713713
Options:
714714
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global]
715+
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
715716
716717
Run "npm help outdated" for more info
717718

0 commit comments

Comments
 (0)