From a6cd70a5b25275d073e5aa5c013a831c7d973cc8 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 16:38:37 +0100 Subject: [PATCH 1/9] feat: add --include to backstage-cli info Signed-off-by: Renas --- packages/cli/cli-report.md | 1 + .../cli/src/modules/info/commands/info.ts | 136 +++++++++++++++++- packages/cli/src/modules/info/index.ts | 15 +- 3 files changed, 143 insertions(+), 9 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index a8df5242ad..fafd8c4f08 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -149,6 +149,7 @@ Usage: Options: --help + --include --version ``` diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 97090d8b9a..8843dd6091 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -19,9 +19,43 @@ import os from 'os'; import { runOutput } from '@backstage/cli-common'; import { paths } from '../../../lib/paths'; import { Lockfile } from '../../../lib/versioning'; +import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; +import { minimatch } from 'minimatch'; import fs from 'fs-extra'; -export default async () => { +interface InfoOptions { + include: string[]; +} + +/** + * Attempts to read package.json from node_modules for a given package name. + * Returns undefined if the package.json cannot be read. + */ +function tryReadPackageJson( + packageName: string, + targetPath: string, +): BackstagePackageJson | undefined { + try { + return require(require.resolve(`${packageName}/package.json`, { + paths: [targetPath], + })); + } catch { + return undefined; + } +} + +/** + * Checks if a package has a backstage field in its package.json + */ +function hasBackstageField( + packageName: string, + targetPath: string, +): boolean { + const pkg = tryReadPackageJson(packageName, targetPath); + return pkg?.backstage !== undefined; +} + +export default async (options: InfoOptions) => { await new Promise(async () => { const yarnVersion = await runOutput(['yarn', '--version']); const isLocal = fs.existsSync(paths.resolveOwn('./src')); @@ -44,16 +78,104 @@ export default async () => { console.log(`cli: ${cliVersion} (${isLocal ? 'local' : 'installed'})`); console.log(`backstage: ${backstageVersion}`); console.log(); - console.log('Dependencies:'); + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); + const targetPath = paths.targetRoot; - const deps = [...lockfile.keys()].filter(n => n.startsWith('@backstage/')); - const maxLength = Math.max(...deps.map(d => d.length)); + // Get workspace package names and their versions + const workspacePackages = new Map(); + try { + const packages = await PackageGraph.listTargetPackages(); + for (const pkg of packages) { + workspacePackages.set(pkg.packageJson.name, pkg.packageJson.version); + } + } catch { + // If we can't list workspace packages, continue without them + } - for (const dep of deps) { - const versions = new Set(lockfile.get(dep)!.map(i => i.version)); - console.log(` ${dep.padEnd(maxLength)} ${[...versions].join(', ')}`); + // Collect all package names from lockfile + const allPackages = [...lockfile.keys()]; + const includePatterns = options.include || []; + + // Collect installed (non-local) packages + const installedDeps = new Set(); + // Collect local workspace packages + const localDeps = new Set(); + + // Process @backstage/* packages + for (const pkg of allPackages) { + if (pkg.startsWith('@backstage/')) { + if (workspacePackages.has(pkg)) { + localDeps.add(pkg); + } else { + installedDeps.add(pkg); + } + } + } + + // Process packages matching --include patterns + for (const pattern of includePatterns) { + for (const pkg of allPackages) { + if (minimatch(pkg, pattern)) { + if (workspacePackages.has(pkg)) { + localDeps.add(pkg); + } else { + installedDeps.add(pkg); + } + } + } + } + + // Process packages with backstage field in their package.json + for (const pkg of allPackages) { + // Skip @backstage/* packages (already processed above) + if (pkg.startsWith('@backstage/')) { + continue; + } + if (workspacePackages.has(pkg)) { + // Check if local package has backstage field + if (hasBackstageField(pkg, targetPath)) { + localDeps.add(pkg); + } + } else if (hasBackstageField(pkg, targetPath)) { + installedDeps.add(pkg); + } + } + + // Helper to get version string for a package + const getVersions = (dep: string): string => { + const entries = lockfile.get(dep); + if (!entries) { + return 'unknown'; + } + const versions = [...new Set(entries.map(i => i.version))]; + return versions.join(', '); + }; + + // Print installed dependencies + console.log('Dependencies:'); + const sortedInstalled = [...installedDeps].sort(); + if (sortedInstalled.length > 0) { + const maxLength = Math.max(...sortedInstalled.map(d => d.length)); + for (const dep of sortedInstalled) { + const versions = getVersions(dep); + console.log(` ${dep.padEnd(maxLength)} ${versions}`); + } + } else { + console.log(' (no installed Backstage packages found)'); + } + + // Print local workspace packages + if (localDeps.size > 0) { + console.log(); + console.log('Local:'); + const sortedLocal = [...localDeps].sort(); + const maxLength = Math.max(...sortedLocal.map(d => d.length)); + for (const dep of sortedLocal) { + const version = workspacePackages.get(dep) ?? 'unknown'; + console.log(` ${dep.padEnd(maxLength)} ${version}`); + } } }); }; diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index e93f008905..9ec4347763 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -24,8 +24,19 @@ export default createCliPlugin({ path: ['info'], description: 'Show helpful information for debugging and reporting bugs', execute: async ({ args }) => { - yargs().parse(args); - await lazy(() => import('./commands/info'), 'default')(args); + const argv = await yargs() + .options({ + include: { + type: 'string', + array: true, + default: [], + description: + 'Glob patterns for additional packages to include (e.g., @spotify/backstage*)', + }, + }) + .help() + .parse(args); + await lazy(() => import('./commands/info'), 'default')(argv); }, }); }, From fd4439a8b865410a79a4d718805b2dd674fb7ccd Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 16:47:05 +0100 Subject: [PATCH 2/9] feat: add --output-file option to backstage-cli info for JSON export Signed-off-by: Renas --- packages/cli/cli-report.md | 22 ++++++++ .../cli/src/modules/info/commands/info.ts | 54 +++++++++++++++---- packages/cli/src/modules/info/index.ts | 5 ++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index fafd8c4f08..6a750f2d7e 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -150,9 +150,31 @@ Usage: Options: --help --include + --output-file --version ``` +#### Examples + +Include additional packages using glob patterns: + +```bash +backstage-cli info --include "@spotify/*" +backstage-cli info --include "@internal/*" --include "@myorg/backstage-*" +``` + +Export the info output to a JSON file: + +```bash +backstage-cli info --output-file backstage-info.json +``` + +Combine both options: + +```bash +backstage-cli info --include "@spotify/*" --output-file backstage-info.json +``` + ### `backstage-cli migrate` ``` diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 8843dd6091..998f9f1000 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -25,6 +25,7 @@ import fs from 'fs-extra'; interface InfoOptions { include: string[]; + outputFile?: string; } /** @@ -67,17 +68,23 @@ export default async (options: InfoOptions) => { const backstageJson = await fs.readJSON(backstageFile); backstageVersion = backstageJson.version ?? 'N/A'; } catch (error) { - console.warn('The "backstage.json" file is not in the expected format'); - console.log(); + if (!options.outputFile) { + console.warn( + 'The "backstage.json" file is not in the expected format', + ); + console.log(); + } } } - console.log(`OS: ${os.type} ${os.release} - ${os.platform}/${os.arch}`); - console.log(`node: ${process.version}`); - console.log(`yarn: ${yarnVersion}`); - console.log(`cli: ${cliVersion} (${isLocal ? 'local' : 'installed'})`); - console.log(`backstage: ${backstageVersion}`); - console.log(); + // Build system info + const systemInfo = { + os: `${os.type} ${os.release} - ${os.platform}/${os.arch}`, + node: process.version, + yarn: yarnVersion, + cli: { version: cliVersion, local: isLocal }, + backstage: backstageVersion, + }; const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); @@ -153,9 +160,37 @@ export default async (options: InfoOptions) => { return versions.join(', '); }; + const sortedInstalled = [...installedDeps].sort(); + const sortedLocal = [...localDeps].sort(); + + // If outputFile is specified, write JSON to file + if (options.outputFile) { + const output = { + system: systemInfo, + dependencies: Object.fromEntries( + sortedInstalled.map(dep => [dep, getVersions(dep)]), + ), + local: Object.fromEntries( + sortedLocal.map(dep => [dep, workspacePackages.get(dep) ?? 'unknown']), + ), + }; + + const outputPath = paths.resolveTargetRoot(options.outputFile); + await fs.writeFile(outputPath, JSON.stringify(output, null, 2)); + console.log(`Info exported to ${outputPath}`); + return; + } + + // Print to console + console.log(`OS: ${systemInfo.os}`); + console.log(`node: ${systemInfo.node}`); + console.log(`yarn: ${systemInfo.yarn}`); + console.log(`cli: ${cliVersion} (${isLocal ? 'local' : 'installed'})`); + console.log(`backstage: ${backstageVersion}`); + console.log(); + // Print installed dependencies console.log('Dependencies:'); - const sortedInstalled = [...installedDeps].sort(); if (sortedInstalled.length > 0) { const maxLength = Math.max(...sortedInstalled.map(d => d.length)); for (const dep of sortedInstalled) { @@ -170,7 +205,6 @@ export default async (options: InfoOptions) => { if (localDeps.size > 0) { console.log(); console.log('Local:'); - const sortedLocal = [...localDeps].sort(); const maxLength = Math.max(...sortedLocal.map(d => d.length)); for (const dep of sortedLocal) { const version = workspacePackages.get(dep) ?? 'unknown'; diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index 9ec4347763..df858cf72d 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -33,6 +33,11 @@ export default createCliPlugin({ description: 'Glob patterns for additional packages to include (e.g., @spotify/backstage*)', }, + 'output-file': { + type: 'string', + description: + 'Write the info output to a JSON file instead of stdout', + }, }) .help() .parse(args); From c0d7bf6e67b2c041ed9b34b10ddc0958b23dedea Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 16:52:09 +0100 Subject: [PATCH 3/9] chore: add changeset Signed-off-by: Renas --- .changeset/fruity-tires-fail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fruity-tires-fail.md diff --git a/.changeset/fruity-tires-fail.md b/.changeset/fruity-tires-fail.md new file mode 100644 index 0000000000..65d3ff26e8 --- /dev/null +++ b/.changeset/fruity-tires-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added `--include` and `--output-file` options to `backstage-cli info` command for including additional packages via glob patterns and exporting output to JSON. From 563035ffe81994bec6c9fe05ac0a9894a8922cf9 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 17:48:13 +0100 Subject: [PATCH 4/9] chore: run prettier Signed-off-by: Renas --- packages/cli/src/modules/info/commands/info.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 998f9f1000..94d6c00250 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -48,10 +48,7 @@ function tryReadPackageJson( /** * Checks if a package has a backstage field in its package.json */ -function hasBackstageField( - packageName: string, - targetPath: string, -): boolean { +function hasBackstageField(packageName: string, targetPath: string): boolean { const pkg = tryReadPackageJson(packageName, targetPath); return pkg?.backstage !== undefined; } @@ -171,7 +168,10 @@ export default async (options: InfoOptions) => { sortedInstalled.map(dep => [dep, getVersions(dep)]), ), local: Object.fromEntries( - sortedLocal.map(dep => [dep, workspacePackages.get(dep) ?? 'unknown']), + sortedLocal.map(dep => [ + dep, + workspacePackages.get(dep) ?? 'unknown', + ]), ), }; From 9aa8fd9c848cf8897cc0c15a1afaaaf5585102d9 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 17:51:39 +0100 Subject: [PATCH 5/9] chore: revert changes to cli-report Signed-off-by: Renas --- packages/cli/cli-report.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 6a750f2d7e..a8df5242ad 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -149,32 +149,9 @@ Usage: Options: --help - --include - --output-file --version ``` -#### Examples - -Include additional packages using glob patterns: - -```bash -backstage-cli info --include "@spotify/*" -backstage-cli info --include "@internal/*" --include "@myorg/backstage-*" -``` - -Export the info output to a JSON file: - -```bash -backstage-cli info --output-file backstage-info.json -``` - -Combine both options: - -```bash -backstage-cli info --include "@spotify/*" --output-file backstage-info.json -``` - ### `backstage-cli migrate` ``` From f59cb84d3482231f501398c86e53947d3658b5d8 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 17:56:01 +0100 Subject: [PATCH 6/9] chore: build api reports Signed-off-by: Renas --- packages/cli/cli-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index a8df5242ad..7dd3de73ca 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -149,6 +149,8 @@ Usage: Options: --help + --include + --output-file --version ``` From cc8e5f52b53e4a495f2fd9dc32432bff87edb359 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 18:00:10 +0100 Subject: [PATCH 7/9] docs: update cli info docs Signed-off-by: Renas --- docs/tooling/cli/03-commands.md | 63 +++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index ce8f862c98..3ab2c678d2 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -432,8 +432,67 @@ Usage: backstage-cli create-github-app Outputs debug information which is useful when opening an issue. Outputs system information, node.js and npm versions, CLI version and type (inside backstage -repo or a created app), all `@backstage/*` package dependency versions. +repo or a created app), all `@backstage/*` package dependency versions, and any +packages that contain a `backstage` field in their `package.json`. + +The command distinguishes between installed packages (from npm) and local +workspace packages, making it easier to understand your Backstage setup. ```text -Usage: backstage-cli info +Usage: backstage-cli info [options] + +Options: + --include Glob patterns for additional packages to include + (e.g., @mycompany/backstage-*) + --output-file Write the info output to a JSON file instead of stdout + -h, --help display help for command +``` + +### Examples + +Output debug information to the console: + +```bash +yarn backstage-cli info +``` + +Include additional packages matching a glob pattern: + +```bash +yarn backstage-cli info --include "@mycompany/*" +``` + +Export information to a JSON file for further processing: + +```bash +yarn backstage-cli info --output-file backstage-info.json +``` + +Combine options to include custom packages and export to JSON: + +```bash +yarn backstage-cli info --include "@mycompany/backstage-*" --include "@internal/*" --output-file debug-info.json +``` + +### JSON Output Format + +When using `--output-file`, the output is structured as follows: + +```json +{ + "system": { + "os": "Darwin 23.0.0 - darwin/arm64", + "node": "v18.17.0", + "yarn": "3.6.0", + "cli": { "version": "0.27.0", "local": false }, + "backstage": "1.20.0" + }, + "dependencies": { + "@backstage/core-plugin-api": "1.8.0", + "@backstage/plugin-catalog": "1.15.0" + }, + "local": { + "@mycompany/backstage-plugin-custom": "0.1.0" + } +} ``` From 4399328c37a5830854987eb86e05db57e1424ba4 Mon Sep 17 00:00:00 2001 From: Renas Date: Mon, 12 Jan 2026 14:26:51 +0100 Subject: [PATCH 8/9] feat: replace --output-file with --format Signed-off-by: Renas --- .changeset/fruity-tires-fail.md | 2 +- docs/tooling/cli/03-commands.md | 22 ++++++++++++++----- packages/cli/cli-report.md | 2 +- .../cli/src/modules/info/commands/info.ts | 12 +++++----- packages/cli/src/modules/info/index.ts | 7 +++--- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.changeset/fruity-tires-fail.md b/.changeset/fruity-tires-fail.md index 65d3ff26e8..a0afd0ed7b 100644 --- a/.changeset/fruity-tires-fail.md +++ b/.changeset/fruity-tires-fail.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Added `--include` and `--output-file` options to `backstage-cli info` command for including additional packages via glob patterns and exporting output to JSON. +Added `--include` and `--format` options to `backstage-cli info` command for including additional packages via glob patterns and outputting as JSON or Text. diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index 3ab2c678d2..07f6dee879 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -444,7 +444,7 @@ Usage: backstage-cli info [options] Options: --include Glob patterns for additional packages to include (e.g., @mycompany/backstage-*) - --output-file Write the info output to a JSON file instead of stdout + --format Output format (default: text) -h, --help display help for command ``` @@ -462,21 +462,33 @@ Include additional packages matching a glob pattern: yarn backstage-cli info --include "@mycompany/*" ``` -Export information to a JSON file for further processing: +Output as JSON: ```bash -yarn backstage-cli info --output-file backstage-info.json +yarn backstage-cli info --format json +``` + +Export JSON to a file for further processing: + +```bash +yarn backstage-cli info --format json > backstage-info.json ``` Combine options to include custom packages and export to JSON: ```bash -yarn backstage-cli info --include "@mycompany/backstage-*" --include "@internal/*" --output-file debug-info.json +yarn backstage-cli info --include "@mycompany/backstage-*" --include "@internal/*" --format json > debug-info.json +``` + +Export text output to a file: + +```bash +yarn backstage-cli info --format text > backstage-info.txt ``` ### JSON Output Format -When using `--output-file`, the output is structured as follows: +When using `--format json`, the output is structured as follows: ```json { diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 7dd3de73ca..f3e077b37e 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -148,9 +148,9 @@ Options: Usage: Options: + --format --help --include - --output-file --version ``` diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 94d6c00250..4e2bda29a7 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -25,7 +25,7 @@ import fs from 'fs-extra'; interface InfoOptions { include: string[]; - outputFile?: string; + format: 'text' | 'json'; } /** @@ -65,7 +65,7 @@ export default async (options: InfoOptions) => { const backstageJson = await fs.readJSON(backstageFile); backstageVersion = backstageJson.version ?? 'N/A'; } catch (error) { - if (!options.outputFile) { + if (options.format !== 'json') { console.warn( 'The "backstage.json" file is not in the expected format', ); @@ -160,8 +160,8 @@ export default async (options: InfoOptions) => { const sortedInstalled = [...installedDeps].sort(); const sortedLocal = [...localDeps].sort(); - // If outputFile is specified, write JSON to file - if (options.outputFile) { + // If format is json, output JSON to stdout + if (options.format === 'json') { const output = { system: systemInfo, dependencies: Object.fromEntries( @@ -175,9 +175,7 @@ export default async (options: InfoOptions) => { ), }; - const outputPath = paths.resolveTargetRoot(options.outputFile); - await fs.writeFile(outputPath, JSON.stringify(output, null, 2)); - console.log(`Info exported to ${outputPath}`); + process.stdout.write(`${JSON.stringify(output, null, 2)}\n`); return; } diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index df858cf72d..c14926df85 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -33,10 +33,11 @@ export default createCliPlugin({ description: 'Glob patterns for additional packages to include (e.g., @spotify/backstage*)', }, - 'output-file': { + format: { type: 'string', - description: - 'Write the info output to a JSON file instead of stdout', + choices: ['text', 'json'], + default: 'text', + description: 'Output format (text or json)', }, }) .help() From 863444a504fda4098259f2db0ed0c2c85bfda34f Mon Sep 17 00:00:00 2001 From: Renas Date: Mon, 19 Jan 2026 15:35:25 +0100 Subject: [PATCH 9/9] feat: change JSON format for dependency versions to array of objects Change the info command's JSON output to use an array of objects for dependency versions instead of comma-separated strings, making the format more parseable and extensible. Co-Authored-By: Claude Opus 4.5 Signed-off-by: Renas --- packages/cli/src/modules/info/commands/info.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 4e2bda29a7..6970699f13 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -147,14 +147,14 @@ export default async (options: InfoOptions) => { } } - // Helper to get version string for a package - const getVersions = (dep: string): string => { + // Helper to get versions for a package as an array of objects + const getVersions = (dep: string): Array<{ version: string }> => { const entries = lockfile.get(dep); if (!entries) { - return 'unknown'; + return [{ version: 'unknown' }]; } const versions = [...new Set(entries.map(i => i.version))]; - return versions.join(', '); + return versions.map(v => ({ version: v })); }; const sortedInstalled = [...installedDeps].sort(); @@ -170,7 +170,7 @@ export default async (options: InfoOptions) => { local: Object.fromEntries( sortedLocal.map(dep => [ dep, - workspacePackages.get(dep) ?? 'unknown', + [{ version: workspacePackages.get(dep) ?? 'unknown' }], ]), ), }; @@ -192,7 +192,9 @@ export default async (options: InfoOptions) => { if (sortedInstalled.length > 0) { const maxLength = Math.max(...sortedInstalled.map(d => d.length)); for (const dep of sortedInstalled) { - const versions = getVersions(dep); + const versions = getVersions(dep) + .map(v => v.version) + .join(', '); console.log(` ${dep.padEnd(maxLength)} ${versions}`); } } else {