From 9457a21b860c106399f1af87c9f4c618f6c5f46b Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Thu, 18 Jul 2019 16:20:31 -0700 Subject: [PATCH] feat(listable): Output JSON adjacency list with `--graph` Credit: @yacineb Original PR: #1970 --- commands/changed/README.md | 1 + commands/list/README.md | 28 +++++++++++++++ .../__tests__/listable-format.test.js | 31 ++++++++++++++++ .../__tests__/listable-options.test.js | 4 +++ utils/listable/lib/listable-format.js | 35 +++++++++++++++++++ utils/listable/lib/listable-options.js | 5 +++ 6 files changed, 104 insertions(+) diff --git a/commands/changed/README.md b/commands/changed/README.md index b200adeb9d..927787150d 100644 --- a/commands/changed/README.md +++ b/commands/changed/README.md @@ -27,6 +27,7 @@ package-2 - [`-l`, `--long`](https://github.com/lerna/lerna/tree/master/commands/list#--long) - [`-p`, `--parseable`](https://github.com/lerna/lerna/tree/master/commands/list#--parseable) - [`--toposort`](https://github.com/lerna/lerna/tree/master/commands/list#--toposort) +- [`--graph`](https://github.com/lerna/lerna/tree/master/commands/list#--graph) Unlike `lerna ls`, however, `lerna changed` **does not** support [filter options](https://www.npmjs.com/package/@lerna/filter-options), as filtering is not supported by `lerna version` or `lerna publish`. diff --git a/commands/list/README.md b/commands/list/README.md index 378e713b72..e747d54b6e 100644 --- a/commands/list/README.md +++ b/commands/list/README.md @@ -32,6 +32,7 @@ In any case, you can always pass `--loglevel silent` to create pristine chains o - [`-l`, `--long`](#--long) - [`-p`, `--parseable`](#--parseable) - [`--toposort`](#--toposort) +- [`--graph`](#--graph) `lerna ls` also respects all available [Filter Flags](https://www.npmjs.com/package/@lerna/filter-options). @@ -143,3 +144,30 @@ $ lerna ls --toposort package-2 package-1 ``` + +### `--graph` + +Show dependency graph as a JSON-formatted [adjacency list](https://en.wikipedia.org/wiki/Adjacency_list). + +```sh +$ lerna ls --graph +{ + "pkg-1": [ + "pkg-2" + ], + "pkg-2": [] +} + +$ lerna ls --graph --all +{ + "pkg-1": [ + "pkg-2" + ], + "pkg-2": [ + "pkg-3" + ], + "pkg-3": [ + "pkg-2" + ] +} +``` diff --git a/utils/listable/__tests__/listable-format.test.js b/utils/listable/__tests__/listable-format.test.js index db41fa19eb..e6762019e9 100644 --- a/utils/listable/__tests__/listable-format.test.js +++ b/utils/listable/__tests__/listable-format.test.js @@ -166,6 +166,37 @@ pkg-3 v3.0.0 pkgs/pkg-3 (PRIVATE) `); }); + test("graph output", () => { + const { text } = formatWithOptions({ graph: true }); + + expect(text).toMatchInlineSnapshot(` + { + "pkg-1": [ + "pkg-2" + ], + "pkg-2": [] + } + `); + }); + + test("all graph output", () => { + const { text } = formatWithOptions({ graph: true, all: true }); + + expect(text).toMatchInlineSnapshot(` + { + "pkg-1": [ + "pkg-2" + ], + "pkg-2": [ + "pkg-3" + ], + "pkg-3": [ + "pkg-2" + ] + } + `); + }); + test("parseable output", () => { const { text } = formatWithOptions({ parseable: true }); diff --git a/utils/listable/__tests__/listable-options.test.js b/utils/listable/__tests__/listable-options.test.js index 90be38e9d5..3f9be27b80 100644 --- a/utils/listable/__tests__/listable-options.test.js +++ b/utils/listable/__tests__/listable-options.test.js @@ -41,4 +41,8 @@ describe("listable.options()", () => { it("provides --toposort", () => { expect(parsed("--toposort")).toHaveProperty("toposort", true); }); + + it("provides --graph", () => { + expect(parsed("--graph")).toHaveProperty("graph", true); + }); }); diff --git a/utils/listable/lib/listable-format.js b/utils/listable/lib/listable-format.js index 61fbd619e7..f0c029a9c0 100644 --- a/utils/listable/lib/listable-format.js +++ b/utils/listable/lib/listable-format.js @@ -20,6 +20,8 @@ function listableFormat(pkgList, options) { text = formatNDJSON(resultList); } else if (viewOptions.showParseable) { text = formatParseable(resultList, viewOptions); + } else if (viewOptions.showGraph) { + text = formatJSONGraph(resultList, viewOptions); } else { text = formatColumns(resultList, viewOptions); } @@ -37,6 +39,7 @@ function parseViewOptions(options) { showNDJSON: options.ndjson, showParseable: options.parseable, isTopological: options.toposort, + showGraph: options.graph, }; } @@ -71,6 +74,38 @@ function formatNDJSON(resultList) { .join("\n"); } +function formatJSONGraph(resultList, viewOptions) { + // https://en.wikipedia.org/wiki/Adjacency_list + const graph = {}; + const getNeighbors = viewOptions.showAll + ? pkg => + Object.keys( + Object.assign( + {}, + pkg.devDependencies, + pkg.peerDependencies, + pkg.optionalDependencies, + pkg.dependencies + ) + ).sort() + : pkg => + Object.keys( + Object.assign( + {}, + // no devDependencies + // no peerDependencies + pkg.optionalDependencies, + pkg.dependencies + ) + ).sort(); + + for (const pkg of resultList) { + graph[pkg.name] = getNeighbors(pkg); + } + + return JSON.stringify(graph, null, 2); +} + function makeParseable(pkg) { const result = [pkg.location, pkg.name]; diff --git a/utils/listable/lib/listable-options.js b/utils/listable/lib/listable-options.js index 9e84b0f09c..012e79ddc5 100644 --- a/utils/listable/lib/listable-options.js +++ b/utils/listable/lib/listable-options.js @@ -37,5 +37,10 @@ function listableOptions(yargs) { describe: "Sort packages in topological order instead of lexical by directory", type: "boolean", }, + graph: { + group: "Command Options:", + describe: "Show dependency graph as a JSON-formatted adjacency list", + type: "boolean", + }, }); }