Skip to content

Commit

Permalink
feat: add ls workspaces
Browse files Browse the repository at this point in the history
- Add listing workspaces deps by default in `npm ls`
- Add ability to filter the result tree by workspace using the -w config
- Added tests and docs

Fixes: npm/statusboard#302
  • Loading branch information
ruyadorno committed May 20, 2021
1 parent 83df366 commit eec6224
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 21 deletions.
22 changes: 22 additions & 0 deletions docs/content/commands/npm-ls.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,28 @@ When used with `npm ls`, only show packages that are linked.
When set to true, npm uses unicode characters in the tree output. When
false, it uses ascii characters instead of unicode glyphs.

#### `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 Down
26 changes: 21 additions & 5 deletions lib/ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const _parent = Symbol('parent')
const _problems = Symbol('problems')
const _required = Symbol('required')
const _type = Symbol('type')
const BaseCommand = require('./base-command.js')
const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js')

class LS extends BaseCommand {
class LS extends ArboristWorkspaceCmd {
/* istanbul ignore next - see test/lib/load-all-commands.js */
static get description () {
return 'List installed packages'
Expand Down Expand Up @@ -50,6 +50,7 @@ class LS extends BaseCommand {
'omit',
'link',
'unicode',
'workspace',
]
}

Expand Down Expand Up @@ -88,6 +89,17 @@ class LS extends BaseCommand {
})
const tree = await this.initTree({arb, args })

// filters by workspaces nodes when using -w <workspace-name>
let filterSet
if (this.workspaces && this.workspaces.length)
filterSet = arb.workspaceDependencySet(tree, this.workspaces)
const filterBySelectedWorkspaces = edge => {
const node = edge && edge.to && (edge.to.target || edge.to)
return !filterSet
|| filterSet.size === 0
|| (node && filterSet.has(node))
}

const seenItems = new Set()
const seenNodes = new Map()
const problems = new Set()
Expand All @@ -109,11 +121,14 @@ class LS extends BaseCommand {
// `nodeResult` is going to be the returned `item` from `visit`
getChildren (node, nodeResult) {
const seenPaths = new Set()
const workspace = node.isWorkspace
const currentDepth = workspace ? 0 : node[_depth]
const shouldSkipChildren =
!(node instanceof Arborist.Node) || (node[_depth] > depthToPrint)
!(node instanceof Arborist.Node) || (currentDepth > depthToPrint)
return (shouldSkipChildren)
? []
: [...(node.target || node).edgesOut.values()]
.filter(filterBySelectedWorkspaces)
.filter(filterByEdgesTypes({
dev,
development,
Expand All @@ -129,7 +144,7 @@ class LS extends BaseCommand {
.sort(sortAlphabetically)
.map(augmentNodesWithMetadata({
args,
currentDepth: node[_depth],
currentDepth,
nodeResult,
seenNodes,
}))
Expand Down Expand Up @@ -257,7 +272,8 @@ const augmentItemWithIncludeMetadata = (node, item) => {

const getHumanOutputItem = (node, { args, color, global, long }) => {
const { pkgid, path } = node
let printable = pkgid
const workspacePkgId = color ? chalk.green(pkgid) : pkgid
let printable = node.isWorkspace ? workspacePkgId : pkgid

// special formatting for top-level package name
if (node.isRoot) {
Expand Down
59 changes: 53 additions & 6 deletions tap-snapshots/test/lib/ls.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -478,17 +478,64 @@ exports[`test/lib/ls.js TAP ls json read problems > should print empty result 1`
`

exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should filter by parent folder workspace config 1`] = `
workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
+-- e@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/e
\`-- f@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/f
`

exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should filter single workspace 1`] = `
filter-by-child-of-missing-dep@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
\`-- a@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a
workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
+-- a@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a
| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
\`-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
`

exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should list workspaces properly 1`] = `
filter-by-child-of-missing-dep@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should filter using workspace config 1`] = `
workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
+-- a@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a
| \`-- c@1.0.0
\`-- b@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/b
| +-- c@1.0.0
| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
\`-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
\`-- foo@1.1.1
\`-- bar@1.0.0
`

exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should list --all workspaces properly 1`] = `
workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
+-- a@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a
| +-- c@1.0.0
| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
+-- b@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/b
+-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
| \`-- foo@1.1.1
| \`-- bar@1.0.0
+-- e@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/e
\`-- f@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/f
`

exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should list workspaces properly with default configs 1`] = `
workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
+-- a@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a
| +-- c@1.0.0
| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
+-- b@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/b
+-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
| \`-- foo@1.1.1
+-- e@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/e
\`-- f@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/f

`

exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should print all tree and filter by dep within only the ws subtree 1`] = `
workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces
\`-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d
\`-- foo@1.1.1
\`-- bar@1.0.0
`

Expand Down
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ All commands:
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [--depth <depth>]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--link]
[--unicode]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
alias: la
Expand Down Expand Up @@ -682,6 +683,7 @@ All commands:
[-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [--depth <depth>]
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--link]
[--unicode]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
alias: list
Expand Down
124 changes: 114 additions & 10 deletions test/lib/ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -1407,14 +1407,16 @@ t.test('ls', (t) => {
})
})

t.test('loading a tree containing workspaces', (t) => {
npm.prefix = t.testdir({
t.test('loading a tree containing workspaces', async (t) => {
npm.localPrefix = npm.prefix = t.testdir({
'package.json': JSON.stringify({
name: 'filter-by-child-of-missing-dep',
name: 'workspaces-tree',
version: '1.0.0',
workspaces: [
'./a',
'./b',
'./d',
'./group/*',
],
}),
node_modules: {
Expand All @@ -1426,13 +1428,29 @@ t.test('ls', (t) => {
version: '1.0.0',
}),
},
d: t.fixture('symlink', '../d'),
e: t.fixture('symlink', '../group/e'),
f: t.fixture('symlink', '../group/f'),
foo: {
'package.json': JSON.stringify({
name: 'foo',
version: '1.1.1',
dependencies: {
bar: '^1.0.0',
},
}),
},
bar: {
'package.json': JSON.stringify({ name: 'bar', version: '1.0.0' }),
},
},
a: {
'package.json': JSON.stringify({
name: 'a',
version: '1.0.0',
dependencies: {
c: '^1.0.0',
d: '^1.0.0',
},
}),
},
Expand All @@ -1442,18 +1460,104 @@ t.test('ls', (t) => {
version: '1.0.0',
}),
},
d: {
'package.json': JSON.stringify({
name: 'd',
version: '1.0.0',
dependencies: {
foo: '^1.1.1',
},
}),
},
group: {
e: {
'package.json': JSON.stringify({
name: 'e',
version: '1.0.0',
}),
},
f: {
'package.json': JSON.stringify({
name: 'f',
version: '1.0.0',
}),
},
},
})

ls.exec([], (err) => {
t.error(err, 'should NOT have ELSPROBLEMS error code')
t.matchSnapshot(redactCwd(result), 'should list workspaces properly')
await new Promise((res, rej) => {
config.all = false
config.depth = 0
npm.color = true
ls.exec([], (err) => {
if (err)
rej(err)

t.matchSnapshot(redactCwd(result),
'should list workspaces properly with default configs')
config.all = true
config.depth = Infinity
npm.color = false
res()
})
})

// --all
await new Promise((res, rej) => {
ls.exec([], (err) => {
if (err)
rej(err)

t.matchSnapshot(redactCwd(result),
'should list --all workspaces properly')
res()
})
})

// filter out a single workspace using args
await new Promise((res, rej) => {
ls.exec(['d'], (err) => {
if (err)
rej(err)

// should also be able to filter out one of the workspaces
ls.exec(['a'], (err) => {
t.error(err, 'should NOT have ELSPROBLEMS error code when filter')
t.matchSnapshot(redactCwd(result), 'should filter single workspace')
res()
})
})

// filter out a single workspace and its deps using workspaces filters
await new Promise((res, rej) => {
ls.execWorkspaces([], ['a'], (err) => {
if (err)
rej(err)

t.matchSnapshot(redactCwd(result),
'should filter using workspace config')
res()
})
})

// filter out a workspace by parent path
await new Promise((res, rej) => {
ls.execWorkspaces([], ['./group'], (err) => {
if (err)
rej(err)

t.matchSnapshot(redactCwd(result),
'should filter by parent folder workspace config')
res()
})
})

// filter by a dep within a workspaces sub tree
await new Promise((res, rej) => {
ls.execWorkspaces(['bar'], ['d'], (err) => {
if (err)
rej(err)

t.end()
t.matchSnapshot(redactCwd(result),
'should print all tree and filter by dep within only the ws subtree')
res()
})
})
})
Expand Down

0 comments on commit eec6224

Please sign in to comment.