From 982193a48acf119e947070af9e313711b549ae63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Tue, 8 Sep 2020 22:39:31 +0200 Subject: [PATCH 1/7] tool list command added json flag to output packages in json format Implements feature request #406 by allowing to type a --json option and let the tree output be in JSON format. The entries consist of the package name as key and either String value (version number + top-level or expanded-above) or an Object with the following properties: - version (String) - always - local (Boolean) - only if true when package is built from source - weak (Boolean) - only if true - newerVersion (String) - only if exists - dependencies (Object) - only if > 0 and not all weak In order to also support a more detailed output, there is a --details option. If it's active, the following properties are added, too: - earliestCompatibleVersion (String) - always - debugOnly (Boolean) - only if true - prodOnly (Boolean) - only if true - testOnly (Boolean) - only if true - containsPlugins (Boolean) - only if true - lastUpdated (Date-String) - always - published (Date-String) - always --- tools/cli/commands-packages.js | 122 ++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 17 deletions(-) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index fc82f4fc28..2e5a00d7d7 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1139,7 +1139,9 @@ main.registerCommand({ requiresApp: true, options: { 'tree': { type: Boolean }, + 'json': { type: Boolean }, 'weak': { type: Boolean }, + 'details': { type: Boolean }, 'allow-incompatible-update': { type: Boolean } }, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: true }) @@ -1154,8 +1156,17 @@ main.registerCommand({ // No need to display the PackageMapDelta here, since we're about to list all // of the packages anyway! - if (options['tree']) { + const showJson = !!options['json']; + const showTree = !!options['tree']; + + if (showJson && showTree) { + throw new Error('can only run for one option,found --json and --tree'); + } + + if (showTree || showJson) { + const jsonOut = showJson && {}; const showWeak = !!options['weak']; + const showDetails = !!options['details']; // Load package details of all used packages (inc. dependencies) const packageDetails = new Map; projectContext.packageMap.eachPackage(function (name, info) { @@ -1172,7 +1183,7 @@ main.registerCommand({ const dontExpand = new Set(topLevelSet.values()); // Recursive function that outputs each package - const printPackage = function (packageToPrint, isWeak, indent1, indent2) { + const printPackage = function ({ packageToPrint, isWeak, indent1, indent2, parent }) { const packageName = packageToPrint.packageName; const depsObj = packageToPrint.dependencies || {}; let deps = Object.keys(depsObj).sort(); @@ -1192,16 +1203,60 @@ main.registerCommand({ const expandedAlready = (deps.length > 0 && dontExpand.has(packageName)); const shouldExpand = (deps.length > 0 && !expandedAlready && !isWeak); - if (indent1 !== '') { - indent1 += (shouldExpand ? '┬' : '─') + ' '; + + // with normal tree display we send the current info to stdout + if (showTree) { + if (indent1 !== '') { + indent1 += (shouldExpand ? '┬' : '─') + ' '; + } + + let suffix = (isWeak ? '[weak]' : ''); + if (expandedAlready) { + suffix += topLevelSet.has(packageName) ? ' (top level)' : ' (expanded above)'; + } + + Console.info(indent1 + packageName + '@' + packageToPrint.version + suffix); } - let suffix = (isWeak ? '[weak]' : ''); - if (expandedAlready) { - suffix += topLevelSet.has(packageName) ? ' (top level)' : ' (expanded above)'; + // with json we add detailed info to the json object + if (showJson) { + if (expandedAlready) { + // on expanded packages we only want to add minimal information to + // keep the json file compact, so we make the value a stirng + if (topLevelSet.has(packageName)) { + parent[packageName] = `${packageToPrint.version}-(top-level)` + } else { + parent[packageName] = `${packageToPrint.version}-(expanded-above)` + } + } else { + // on non-expanded packages we want detailed information but we + // omit falsy values in order to keep the output minimal and readable + const entry = {}; + parent[packageName] = entry; + + Object.entries({ + version: packageToPrint.version, + weak: isWeak, + newerVersion: getNewerVersion(packageName, packageToPrint.version, catalog.official), + earliestCompatibleVersion: showDetails && packageToPrint.earliestCompatibleVersion, + debugOnly: showDetails && packageToPrint.debugOnly, + prodOnly: showDetails && packageToPrint.prodOnly, + testOnly: showDetails && packageToPrint.testOnly, + containsPlugins: showDetails && packageToPrint.containsPlugins, + lastUpdated: showDetails && packageToPrint.lastUpdated, + published: showDetails && packageToPrint.published, + }).forEach(([key, value]) => { + if (value) { + entry[key] = value; + } + }); + + if (shouldExpand) { + entry.dependencies = {}; + } + } } - Console.info(indent1 + packageName + '@' + packageToPrint.version + suffix); if (shouldExpand) { dontExpand.add(packageName); deps.forEach((dep, index) => { @@ -1209,14 +1264,37 @@ main.registerCommand({ const weakRef = references.length > 0 && references.every(r => r.weak); const last = ((index + 1) === deps.length); const child = packageDetails.get(dep); - const newIndent1 = indent2 + (last ? '└─' : '├─'); - const newIndent2 = indent2 + (last ? ' ' : '│ '); - if (child) { - printPackage(child, weakRef, newIndent1, newIndent2); - } else if (weakRef) { - Console.info(newIndent1 + '─ ' + dep + '[weak] package skipped'); - } else { - Console.info(newIndent1 + '─ ' + dep + ' missing?'); + + // with normal tree display we increase indentation + if (showTree) { + const newIndent1 = indent2 + (last ? '└─' : '├─'); + const newIndent2 = indent2 + (last ? ' ' : '│ '); + if (child) { + printPackage({ + packageToPrint: child, + isWeak: weakRef, + indent1: newIndent1, + indent2: newIndent2 + }); + } else if (weakRef) { + Console.info(newIndent1 + '─ ' + dep + '[weak] package skipped'); + } else { + Console.info(newIndent1 + '─ ' + dep + ' missing?'); + } + } + + if (showJson) { + if (child) { + printPackage({ + packageToPrint: child, + isWeak: weakRef, + parent: parent[packageName].dependencies + }); + } else if (weakRef) { + parent[packageName].dependencies[dep] = '[weak]-package-skipped'; + } else { + parent[packageName].dependencies = 'missing?'; + } } }); } @@ -1228,10 +1306,20 @@ main.registerCommand({ if (topLevelPackage) { // Force top level packages to be expanded dontExpand.delete(topLevelPackage.packageName); - printPackage(topLevelPackage, false, '', ''); + printPackage({ + packageToPrint: topLevelPackage, + isWeak: false, + indent1: '', + indent2: '', + parent: jsonOut + }) } }); + if (showJson) { + Console.info(JSON.stringify(jsonOut)) + } + return 0; } From 6803b50defdfcd7bce7ab97fb8fc10e279c7655a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Tue, 8 Sep 2020 23:05:37 +0200 Subject: [PATCH 2/7] tools list command added mising local entry to pacakge entry in json mode --- tools/cli/commands-packages.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 2e5a00d7d7..ff78be79e3 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1234,8 +1234,11 @@ main.registerCommand({ const entry = {}; parent[packageName] = entry; + const mapInfo = projectContext.packageMap.getInfo(packageName); + Object.entries({ version: packageToPrint.version, + local: mapInfo && mapInfo.kind === 'local', weak: isWeak, newerVersion: getNewerVersion(packageName, packageToPrint.version, catalog.official), earliestCompatibleVersion: showDetails && packageToPrint.earliestCompatibleVersion, From 095f1523ca8a3bbb906fb58823d9f04cfb3edcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Tue, 8 Sep 2020 23:06:29 +0200 Subject: [PATCH 3/7] tools list command update help text with json flag --- tools/cli/help.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/cli/help.txt b/tools/cli/help.txt index a0618387e0..c1daf8e276 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -289,13 +289,17 @@ Options: List the packages explicitly used by your project. Usage: meteor list meteor list --tree [--weak] + meteor list --json [--weak] [--details] Lists the packages that you have explicitly added to your project. Transitive dependencies are not listed unless you use the --tree option, which outputs a tree showing how packages are referenced. +Use the --json option to output the tree in JSON format. + Options: --weak Show weakly referenced dependencies in the tree. + --details Adds more package details to the JSON output. >>> add-platform Add a platform to this project. From 8937648742677fc79fc7278a059b059df9195b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Wed, 23 Sep 2020 14:29:32 +0200 Subject: [PATCH 4/7] tools list command simplified attaching details --- tools/cli/commands-packages.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index ff78be79e3..5074bc3e29 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1235,20 +1235,14 @@ main.registerCommand({ parent[packageName] = entry; const mapInfo = projectContext.packageMap.getInfo(packageName); - - Object.entries({ + const infoSource = Object.assign({}, showDetails ? packageToPrint : {}, { version: packageToPrint.version, local: mapInfo && mapInfo.kind === 'local', weak: isWeak, - newerVersion: getNewerVersion(packageName, packageToPrint.version, catalog.official), - earliestCompatibleVersion: showDetails && packageToPrint.earliestCompatibleVersion, - debugOnly: showDetails && packageToPrint.debugOnly, - prodOnly: showDetails && packageToPrint.prodOnly, - testOnly: showDetails && packageToPrint.testOnly, - containsPlugins: showDetails && packageToPrint.containsPlugins, - lastUpdated: showDetails && packageToPrint.lastUpdated, - published: showDetails && packageToPrint.published, - }).forEach(([key, value]) => { + newerVersion: getNewerVersion(packageName, packageToPrint.version, catalog.official) + }); + + Object.entries(infoSource).forEach(([key, value]) => { if (value) { entry[key] = value; } From 4f39ab90bb6ce7a4262b4eeabdfb8864f4e68a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Wed, 23 Sep 2020 14:31:07 +0200 Subject: [PATCH 5/7] tools list fix unwanted pretty-print of json output --- tools/cli/commands-packages.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 5074bc3e29..4377631661 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1314,7 +1314,10 @@ main.registerCommand({ }); if (showJson) { - Console.info(JSON.stringify(jsonOut)) + // we can't use Console here, because it pretty prints the output with + // a wrap at 80 chars per line, which causes the json to break if details + // options is active and the package descriptions exceed the limit + console.info(JSON.stringify(jsonOut)); } return 0; From 7bc487421f4598b028665b178df5171cb80244f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Sat, 17 Oct 2020 15:07:45 +0200 Subject: [PATCH 6/7] tool list command uses same suffixes for different output modes In order to avoid repitition we use a single source of suffixes for the list --tree and list --json commands. --- tools/cli/commands-packages.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 4377631661..55dd4b5697 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1159,6 +1159,14 @@ main.registerCommand({ const showJson = !!options['json']; const showTree = !!options['tree']; + const suffixes = { + topLevel: '(top level)', + expandedAbove: '(expanded above)', + weak: '[weak]', + skipped: 'package skipped', + missing: 'missing?' + } + if (showJson && showTree) { throw new Error('can only run for one option,found --json and --tree'); } @@ -1210,9 +1218,11 @@ main.registerCommand({ indent1 += (shouldExpand ? '┬' : '─') + ' '; } - let suffix = (isWeak ? '[weak]' : ''); + let suffix = (isWeak ? suffixes.weak : ''); if (expandedAlready) { - suffix += topLevelSet.has(packageName) ? ' (top level)' : ' (expanded above)'; + suffix += topLevelSet.has(packageName) + ? ` ${suffixes.topLevel}` + : ` ${suffixes.expandedAbove}` } Console.info(indent1 + packageName + '@' + packageToPrint.version + suffix); @@ -1224,9 +1234,9 @@ main.registerCommand({ // on expanded packages we only want to add minimal information to // keep the json file compact, so we make the value a stirng if (topLevelSet.has(packageName)) { - parent[packageName] = `${packageToPrint.version}-(top-level)` + parent[packageName] = `${packageToPrint.version}-${suffixes.topLevel}` } else { - parent[packageName] = `${packageToPrint.version}-(expanded-above)` + parent[packageName] = `${packageToPrint.version}-${suffixes.expandedAbove}` } } else { // on non-expanded packages we want detailed information but we @@ -1274,9 +1284,9 @@ main.registerCommand({ indent2: newIndent2 }); } else if (weakRef) { - Console.info(newIndent1 + '─ ' + dep + '[weak] package skipped'); + Console.info(`${newIndent1}─ ${dep} ${suffixes.weak} ${suffixes.skipped}`); } else { - Console.info(newIndent1 + '─ ' + dep + ' missing?'); + Console.info(`${newIndent1}─ ${dep} ${suffixes.missing}`); } } @@ -1288,9 +1298,9 @@ main.registerCommand({ parent: parent[packageName].dependencies }); } else if (weakRef) { - parent[packageName].dependencies[dep] = '[weak]-package-skipped'; + parent[packageName].dependencies[dep] = `${suffixes.weak} ${suffixes.skipped}`; } else { - parent[packageName].dependencies = 'missing?'; + parent[packageName].dependencies = suffixes.missing; } } }); From b30207bbe088ea25bd12deb5950ad20176d5631b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=BCster?= Date: Sat, 17 Oct 2020 15:09:29 +0200 Subject: [PATCH 7/7] tools test list command added simple tests for tree and json mode We want at least test, that there is a minimal integrity with these commands when running the arguments parsing tests. --- tools/tests/command-line.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/tests/command-line.js b/tools/tests/command-line.js index 94cc8a1a18..518015ac86 100644 --- a/tools/tests/command-line.js +++ b/tools/tests/command-line.js @@ -347,10 +347,25 @@ selftest.define("argument parsing", function () { s.createApp('myapp', 'standard-app'); s.cd('myapp', function () { run = s.run("list"); - run.waitSecs(60); + run.waitSecs(20); run.expectExit(0); }); + s.cd('myapp', function () { + run = s.run("list", "--tree"); + run.waitSecs(20); + run.match("├─┬") + run.match("│ ├─┬") + run.expectExit(0); + }) + + s.cd('myapp', function () { + run = s.run("list", "--json"); + run.waitSecs(20); + run.match(/[{}"a-zA-Z0-9,\s\n\r:_.()\[\]]+/) + run.expectExit(0); + }) + s.createApp("app-with-extra-packages", "extra-packages-option", { dontPrepareApp: true });