From 3a82335843b6980b0a23be43fcb4e6e0d16c601f Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 12 May 2021 17:30:09 -0400 Subject: [PATCH] feat: add support to filtering by workspaces Adds support to a new `workspaces` option, that accepts an array of strings, allowing users to filter the resulting fund info to a specific set of workspaces and their dependencies. Relates to: https://github.com/npm/statusboard/issues/301 --- README.md | 5 ++- index.js | 24 +++++++++++- test.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c373a9c..8ab663f 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,10 @@ Options: - `countOnly`: Uses the tree-traversal logic from **npm fund** but skips over any obj definition and just returns an obj containing `{ length }` - useful for things such as printing a `6 packages are looking for funding` msg. -- `path`: Location to current working directory +- `workspaces`: `Array` List of workspaces names to filter for, +the result will only include a subset of the resulting tree that includes +only the nodes that are children of the listed workspaces names. +- `path`, `registry` and more [Arborist](https://github.com/npm/arborist/) options. ##### `> fund.readTree(tree, [opts]) -> Promise` diff --git a/index.js b/index.js index 49e68dc..3a70ecf 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const URL = require('url').URL const Arborist = require('@npmcli/arborist') +const { resolve } = require('path') // supports object funding and string shorthand, or an array of these // if original was an array, returns an array; else returns the lone item @@ -38,7 +39,27 @@ function isValidFunding (funding) { const empty = () => Object.create(null) +const filterWorkspaces = (tree, opts) => { + if (opts && opts.workspaces && opts.workspaces.length) { + const workspaces = new Set(opts.workspaces) + const workspacesPaths = + new Set(opts.workspaces.map(w => resolve(opts.path, w))) + + const isFiltered = node => + workspaces.has(node.name) || workspacesPaths.has(node.target.path) + + for (const edge of tree.edgesOut.values()) { + const node = edge.to + + if (!node.isWorkspace || !isFiltered(node)) + node.parent = null + } + } + return tree +} + function readTree (tree, opts) { + tree = filterWorkspaces(tree, opts) let packageWithFundingCount = 0 const seen = new Set() const { countOnly } = opts || {} @@ -174,8 +195,7 @@ function readTree (tree, opts) { async function read (opts) { const arb = new Arborist(opts) - const tree = await arb.loadActual() - + const tree = await arb.loadActual(opts) return readTree(tree, opts) } diff --git a/test.js b/test.js index b86491b..59b8646 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,6 @@ 'use strict' +const { resolve } = require('path') const t = require('tap') const { read, @@ -1261,3 +1262,110 @@ t.test('invalid funding objects', (t) => { ) t.end() }) + +t.test('workspaces', t => { + t.test('filter by workspace', async t => { + const path = t.testdir({ + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: [ + 'packages/*', + ], + dependencies: { + '@npmcli/baz': '^1.0.0', + }, + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + funding: 'http://example.com/a', + dependencies: { + '@npmcli/foo': '^1.0.0', + }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + devDependencies: { + '@npmcli/bar': '^1.0.0', + }, + }), + }, + }, + node_modules: { + a: t.fixture('symlink', '../packages/a'), + b: t.fixture('symlink', '../packages/b'), + '@npmcli': { + foo: { + 'package.json': JSON.stringify({ + name: '@npmcli/foo', + version: '1.0.0', + funding: 'http://example.com/foo', + }), + }, + bar: { + 'package.json': JSON.stringify({ + name: '@npmcli/bar', + version: '1.0.0', + funding: 'http://example.com/bar', + }), + }, + baz: { + 'package.json': JSON.stringify({ + name: '@npmcli/baz', + version: '1.0.0', + funding: 'http://example.com/baz', + }), + }, + }, + }, + }) + + const expected = { + dependencies: { + a: { + funding: { + url: 'http://example.com/a', + }, + version: '1.0.0', + dependencies: { + '@npmcli/foo': { + funding: { + url: 'http://example.com/foo', + }, + version: '1.0.0', + }, + }, + }, + }, + length: 2, + name: 'root', + version: '1.0.0', + } + + t.same( + await read({ path, workspaces: ['a'] }), + expected, + 'should filter by workspace name' + ) + + t.same( + await read({ path, workspaces: ['./packages/a'] }), + expected, + 'should filter by workspace path' + ) + + t.same( + await read({ path, workspaces: [resolve(path, 'packages/a')] }), + expected, + 'should filter by workspace full path' + ) + }) + + t.end() +})