diff --git a/docs/cli/commands.md b/docs/cli/commands.md index ff61e51c75..a27cc62712 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -57,6 +57,7 @@ postpack Restores the changes made by the prepack command create-github-app Create new GitHub App in your organization (experimental) +info Show helpful information for debugging and reporting bugs help [command] display help for command ``` @@ -647,3 +648,15 @@ YAML file that can be referenced in the GitHub integration configuration. ```text Usage: backstage-cli create-github-app <github-org> ``` + +## info + +Scope: `root` + +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. + +```text +Usage: backstage-cli info +``` diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index d21349a4e1..bcd1509f58 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -224,6 +224,11 @@ export function registerCommands(program: CommanderStatic) { .command('create-github-app ') .description('Create new GitHub App in your organization.') .action(lazy(() => import('./create-github-app').then(m => m.default))); + + program + .command('info') + .description('Show helpful information for debugging and reporting bugs') + .action(lazy(() => import('./info').then(m => m.default))); } // Wraps an action function so that it always exits and handles errors diff --git a/packages/cli/src/commands/info.ts b/packages/cli/src/commands/info.ts new file mode 100644 index 0000000000..4042d2cb8e --- /dev/null +++ b/packages/cli/src/commands/info.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { version as cliVersion } from '../../package.json'; +import os from 'os'; +import { runPlain } from '../lib/run'; +import { paths } from '../lib/paths'; +import { Lockfile } from '../lib/versioning'; + +export default async () => { + await new Promise(async () => { + const yarnVersion = await runPlain('yarn --version'); + // eslint-disable-next-line no-restricted-syntax + const isLocal = require('fs').existsSync(paths.resolveOwn('./src')); + + 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(); + console.log('Dependencies:'); + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + + const deps = [...lockfile.keys()].filter(n => n.startsWith('@backstage/')); + const maxLength = Math.max(...deps.map(d => d.length)); + + for (const dep of deps) { + const versions = new Set(lockfile.get(dep)!.map(i => i.version)); + console.log(` ${dep.padEnd(maxLength)} ${[...versions].join(', ')}`); + } + }); +};