diff --git a/automation/capitanodoc/capitanodoc.ts b/automation/capitanodoc/capitanodoc.ts index 5dcb840f..f7df4003 100644 --- a/automation/capitanodoc/capitanodoc.ts +++ b/automation/capitanodoc/capitanodoc.ts @@ -58,12 +58,8 @@ const capitanoDoc = { files: ['build/actions/tags.js'], }, { - title: 'Help', - files: ['build/actions/help.js'], - }, - { - title: 'Information', - files: ['build/actions/info.js'], + title: 'Help and Version', + files: ['build/actions/help.js', 'build/actions-oclif/version.js'], }, { title: 'Keys', diff --git a/doc/cli.markdown b/doc/cli.markdown index c6b4ba97..8dbb4e94 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -119,12 +119,9 @@ If you come across any problems or would like to get in touch: - [tag set <tagKey> [value]](#tag-set-tagkey-value) - [tag rm <tagKey>](#tag-rm-tagkey) -- Help +- Help and Version - [help [command...]](#help-command) - -- Information - - [version](#version) - Keys @@ -779,7 +776,7 @@ device uuid release id -# Help +# Help and Version ## help [command...] @@ -796,11 +793,27 @@ Examples: show additional commands -# Information - ## version -Display the balena CLI version. +Display version information for the balena CLI and/or Node.js. +If you intend to parse the output, please use the -j option for +JSON output, as its format is more stable. + +Examples: + + $ balena version + $ balena version -a + $ balena version -j + +### Options + +#### -a, --all + +include version information for additional components (Node.js) + +#### -j, --json + +output version information in JSON format for programmatic use # Keys diff --git a/lib/actions-oclif/version.ts b/lib/actions-oclif/version.ts new file mode 100644 index 00000000..b2db227f --- /dev/null +++ b/lib/actions-oclif/version.ts @@ -0,0 +1,79 @@ +/** + * @license + * Copyright 2019 Balena Ltd. + * + * 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 { Command, flags } from '@oclif/command'; +import { stripIndent } from 'common-tags'; + +interface FlagsDef { + all?: boolean; + json?: boolean; +} + +export default class VersionCmd extends Command { + public static description = stripIndent` + Display version information for the balena CLI and/or Node.js. + + Display version information for the balena CLI and/or Node.js. + If you intend to parse the output, please use the -j option for + JSON output, as its format is more stable. +`; + public static examples = [ + '$ balena version', + '$ balena version -a', + '$ balena version -j', + ]; + + public static usage = 'version'; + + public static flags = { + all: flags.boolean({ + char: 'a', + default: false, + description: + 'include version information for additional components (Node.js)', + }), + json: flags.boolean({ + char: 'j', + default: false, + description: + 'output version information in JSON format for programmatic use', + }), + help: flags.help({ char: 'h' }), + }; + + public async run() { + const { flags: options } = this.parse(VersionCmd); + const versions = { + 'balena-cli': (await import('../../package.json')).version, + 'Node.js': + process.version && process.version.startsWith('v') + ? process.version.slice(1) + : process.version, + }; + if (options.json) { + console.log(JSON.stringify(versions, null, 4)); + } else { + if (options.all) { + console.log(`balena-cli version "${versions['balena-cli']}"`); + console.log(`Node.js version "${versions['Node.js']}"`); + } else { + // backwards compatibility + console.log(versions['balena-cli']); + } + } + } +} diff --git a/lib/actions/help_ts.ts b/lib/actions/help_ts.ts index aaf30ea9..deacba62 100644 --- a/lib/actions/help_ts.ts +++ b/lib/actions/help_ts.ts @@ -18,10 +18,14 @@ import { Command } from '@oclif/command'; import * as _ from 'lodash'; -import EnvAddCmd from '../actions-oclif/env/add'; - -export function getOclifHelpLinePairs(): [[string, string]] { - return [getCmdUsageDescriptionLinePair(EnvAddCmd)]; +export function getOclifHelpLinePairs(): Array<[string, string]> { + // Although it's tempting to have these oclif commands 'require'd in a + // central place, it would impact on performance (CLI start time). An + // improvement would probably be to automatically scan the actions-oclif + // folder. + const EnvAddCmd = require('../actions-oclif/env/add').default; + const VersionCmd = require('../actions-oclif/version').default; + return [EnvAddCmd, VersionCmd].map(getCmdUsageDescriptionLinePair); } function getCmdUsageDescriptionLinePair(cmd: typeof Command): [string, string] { diff --git a/lib/actions/index.coffee b/lib/actions/index.coffee index b3b3ffcf..9ad6c7eb 100644 --- a/lib/actions/index.coffee +++ b/lib/actions/index.coffee @@ -18,7 +18,6 @@ module.exports = apiKey: require('./api-key') app: require('./app') auth: require('./auth') - info: require('./info') device: require('./device') env: require('./environment-variables') tags: require('./tags') diff --git a/lib/actions/info.ts b/lib/actions/info.ts deleted file mode 100644 index cac150d1..00000000 --- a/lib/actions/info.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2016-2017 Balena - -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 { CommandDefinition } from 'capitano'; - -export const version: CommandDefinition = { - signature: 'version', - description: 'output the version number', - help: `\ -Display the balena CLI version.\ -`, - async action(_params, _options, done) { - const packageJSON = await import('../../package.json'); - console.log(packageJSON.version); - return done(); - }, -}; diff --git a/lib/app-capitano.coffee b/lib/app-capitano.coffee index 5b220eaa..1d0cddce 100644 --- a/lib/app-capitano.coffee +++ b/lib/app-capitano.coffee @@ -33,8 +33,10 @@ capitano.globalOption boolean: true alias: 'h' -# ---------- Info Module ---------- -capitano.command(actions.info.version) +capitano.globalOption + signature: 'version' + boolean: true + alias: 'v' # ---------- Help Module ---------- capitano.command(actions.help.help) diff --git a/lib/app-oclif.ts b/lib/app-oclif.ts index 5ade084b..8ae27ea8 100644 --- a/lib/app-oclif.ts +++ b/lib/app-oclif.ts @@ -15,23 +15,34 @@ * limitations under the License. */ +import { Main } from '@oclif/command'; import { ExitError } from '@oclif/errors'; import { handleError } from './errors'; +class CustomMain extends Main { + protected _helpOverride(): boolean { + // Disable oclif's default handler for the 'version' command + if (['-v', '--version', 'version'].includes(this.argv[0])) { + return false; + } else { + return super._helpOverride(); + } + } +} + /** * oclif CLI entrypoint */ export function run(argv: string[]) { - process.argv = argv; - require('@oclif/command') - .run() - .then(require('@oclif/command/flush')) - .catch((error: Error) => { + CustomMain.run(argv.slice(2)).then( + require('@oclif/command/flush'), + (error: Error) => { // oclif sometimes exits with ExitError code 0 (not an error) if (error instanceof ExitError && error.oclif.exit === 0) { return; } handleError(error); - }); + }, + ); } diff --git a/lib/app.ts b/lib/app.ts index 6353aca8..f2923f26 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -31,21 +31,29 @@ function routeCliFramework(argv: string[]): void { ); } const cmdSlice = argv.slice(2); - let isOclif = false; // Look for commands that have been deleted, to print a notice checkDeletedCommand(cmdSlice); - if (cmdSlice.length > 1) { + if (cmdSlice.length > 0) { + // convert 'balena --version' or 'balena -v' to 'balena version' + if (['--version', '-v'].includes(cmdSlice[0])) { + cmdSlice[0] = 'version'; + } + // convert 'balena --help' or 'balena -h' to 'balena help' + else if (['--help', '-h'].includes(cmdSlice[0])) { + cmdSlice[0] = 'help'; + } // convert e.g. 'balena help env add' to 'balena env add --help' - if (cmdSlice[0] === 'help') { + if (cmdSlice.length > 1 && cmdSlice[0] === 'help') { cmdSlice.shift(); cmdSlice.push('--help'); } + } - // Look for commands that have been transitioned to oclif - isOclif = isOclifCommand(cmdSlice); - if (isOclif) { + const [isOclif, isTopic] = isOclifCommand(cmdSlice); + if (isOclif) { + if (isTopic) { // convert space-separated commands to oclif's topic:command syntax argv = [ argv[0], @@ -53,11 +61,11 @@ function routeCliFramework(argv: string[]): void { cmdSlice[0] + ':' + cmdSlice[1], ...cmdSlice.slice(2), ]; + } else { + argv = [argv[0], argv[1], ...cmdSlice]; } - } - if (isOclif) { if (process.env.DEBUG) { - console.log(`Debug: oclif new argv=[${argv}] length=${argv.length}`); + console.log(`Debug: new argv=[${argv}] length=${argv.length}`); } require('./app-oclif').run(argv); } else { @@ -113,18 +121,28 @@ function checkDeletedCommand(argvSlice: string[]): void { } /** - * Determine whether the CLI command has been converted from Capitano to ocif. + * Determine whether the CLI command has been converted from Capitano to oclif. + * Return an array of two boolean values: + * r[0] : whether the CLI command is implemented with oclif + * r[1] : if r[0] is true, whether the CLI command is implemented with + * oclif "topics" (colon-separated subcommands like `env:add`) * @param argvSlice process.argv.slice(2) */ -function isOclifCommand(argvSlice: string[]): boolean { +function isOclifCommand(argvSlice: string[]): [boolean, boolean] { // Look for commands that have been transitioned to oclif - if (argvSlice.length > 1) { - // balena env add - if (argvSlice[0] === 'env' && argvSlice[1] === 'add') { - return true; + if (argvSlice.length > 0) { + // balena version + if (argvSlice[0] === 'version') { + return [true, false]; + } + if (argvSlice.length > 1) { + // balena env add + if (argvSlice[0] === 'env' && argvSlice[1] === 'add') { + return [true, true]; + } } } - return false; + return [false, false]; } /** diff --git a/lib/utils/oclif-utils.ts b/lib/utils/oclif-utils.ts index e507cffe..9093debe 100644 --- a/lib/utils/oclif-utils.ts +++ b/lib/utils/oclif-utils.ts @@ -27,7 +27,7 @@ export const convertedCommands = { * command help output. */ export class CommandHelp { - constructor(public command: { args: any[] }) {} + constructor(public command: { args?: any[] }) {} protected arg(arg: Config.Command['args'][0]): string { const name = arg.name.toUpperCase(); @@ -40,7 +40,7 @@ export class CommandHelp { public defaultUsage(): string { return CommandHelp.compact([ // this.command.id, - this.command.args + (this.command.args || []) .filter(a => !a.hidden) .map(a => this.arg(a)) .join(' '), diff --git a/package.json b/package.json index fe1460eb..2cbbe65f 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "node_modules/raven/lib/instrumentation/*.js" ], "assets": [ + "build/actions-oclif", "build/auth/pages/*.ejs", "node_modules/resin-discoverable-services/services/**/*" ]