mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-19 21:57:51 +00:00
Convert help to oclif, remove capitano
Change-type: patch Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
cd81ff005f
commit
d56fec6e36
@ -88,7 +88,7 @@ const capitanoDoc = {
|
||||
},
|
||||
{
|
||||
title: 'Help and Version',
|
||||
files: ['build/actions/help.js', 'build/actions-oclif/version.js'],
|
||||
files: ['help', 'build/actions-oclif/version.js'],
|
||||
},
|
||||
{
|
||||
title: 'Keys',
|
||||
|
5
automation/capitanodoc/doc-types.d.ts
vendored
5
automation/capitanodoc/doc-types.d.ts
vendored
@ -15,7 +15,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Command as OclifCommandClass } from '@oclif/command';
|
||||
import { CommandDefinition as CapitanoCommand } from 'capitano';
|
||||
|
||||
type OclifCommand = typeof OclifCommandClass;
|
||||
|
||||
@ -27,7 +26,7 @@ export interface Document {
|
||||
|
||||
export interface Category {
|
||||
title: string;
|
||||
commands: Array<CapitanoCommand | OclifCommand>;
|
||||
commands: OclifCommand[];
|
||||
}
|
||||
|
||||
export { CapitanoCommand, OclifCommand };
|
||||
export { OclifCommand };
|
||||
|
@ -14,12 +14,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
|
||||
import { getCapitanoDoc } from './capitanodoc';
|
||||
import { CapitanoCommand, Category, Document, OclifCommand } from './doc-types';
|
||||
import { Category, Document, OclifCommand } from './doc-types';
|
||||
import * as markdown from './markdown';
|
||||
import { stripIndent } from '../../lib/utils/lazy';
|
||||
|
||||
/**
|
||||
* Generates the markdown document (as a string) for the CLI documentation
|
||||
@ -40,11 +39,7 @@ export async function renderMarkdown(): Promise<string> {
|
||||
};
|
||||
|
||||
for (const jsFilename of commandCategory.files) {
|
||||
category.commands.push(
|
||||
...(jsFilename.includes('actions-oclif')
|
||||
? importOclifCommands(jsFilename)
|
||||
: importCapitanoCommands(jsFilename)),
|
||||
);
|
||||
category.commands.push(...importOclifCommands(jsFilename));
|
||||
}
|
||||
result.categories.push(category);
|
||||
}
|
||||
@ -52,27 +47,48 @@ export async function renderMarkdown(): Promise<string> {
|
||||
return markdown.render(result);
|
||||
}
|
||||
|
||||
function importCapitanoCommands(jsFilename: string): CapitanoCommand[] {
|
||||
const actions = require(path.join(process.cwd(), jsFilename));
|
||||
const commands: CapitanoCommand[] = [];
|
||||
// Help is now managed via a plugin
|
||||
// This fake command allows capitanodoc to include help in docs
|
||||
class FakeHelpCommand {
|
||||
description = stripIndent`
|
||||
List balena commands, or get detailed help for an specific command.
|
||||
|
||||
if (actions.signature) {
|
||||
commands.push(_.omit(actions, 'action') as any);
|
||||
} else {
|
||||
for (const actionName of Object.keys(actions)) {
|
||||
const actionCommand = actions[actionName];
|
||||
commands.push(_.omit(actionCommand, 'action') as any);
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
List balena commands, or get detailed help for an specific command.
|
||||
`;
|
||||
|
||||
examples = [
|
||||
'$ balena help',
|
||||
'$ balena help apps',
|
||||
'$ balena help os download',
|
||||
];
|
||||
|
||||
args = [
|
||||
{
|
||||
name: 'command',
|
||||
description: 'command to show help for',
|
||||
},
|
||||
];
|
||||
|
||||
usage = 'help [command]';
|
||||
|
||||
flags = {
|
||||
verbose: {
|
||||
description: 'show additional commands',
|
||||
char: '-v',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function importOclifCommands(jsFilename: string): OclifCommand[] {
|
||||
// TODO: Currently oclif commands with no `usage` overridden will cause
|
||||
// an error when parsed. This should be improved so that `usage` does not have
|
||||
// to be overridden if not necessary.
|
||||
const command: OclifCommand = require(path.join(process.cwd(), jsFilename))
|
||||
.default as OclifCommand;
|
||||
|
||||
const command: OclifCommand =
|
||||
jsFilename === 'help'
|
||||
? ((new FakeHelpCommand() as unknown) as OclifCommand)
|
||||
: (require(path.join(process.cwd(), jsFilename)).default as OclifCommand);
|
||||
|
||||
return [command];
|
||||
}
|
||||
|
||||
|
@ -20,33 +20,10 @@ import * as _ from 'lodash';
|
||||
|
||||
import { getManualSortCompareFunction } from '../../lib/utils/helpers';
|
||||
import { capitanoizeOclifUsage } from '../../lib/utils/oclif-utils';
|
||||
import { CapitanoCommand, Category, Document, OclifCommand } from './doc-types';
|
||||
import * as utils from './utils';
|
||||
|
||||
function renderCapitanoCommand(command: CapitanoCommand): string[] {
|
||||
const result = [`## ${ent.encode(command.signature)}`, command.help!];
|
||||
|
||||
if (!_.isEmpty(command.options)) {
|
||||
result.push('### Options');
|
||||
|
||||
for (const option of command.options!) {
|
||||
if (option == null) {
|
||||
throw new Error(`Undefined option in markdown generation!`);
|
||||
}
|
||||
if (option.description == null) {
|
||||
throw new Error(`Undefined option.description in markdown generation!`);
|
||||
}
|
||||
result.push(
|
||||
`#### ${utils.parseCapitanoOption(option)}`,
|
||||
option.description,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
import { Category, Document, OclifCommand } from './doc-types';
|
||||
|
||||
function renderOclifCommand(command: OclifCommand): string[] {
|
||||
const result = [`## ${ent.encode(command.usage)}`];
|
||||
const result = [`## ${ent.encode(command.usage || '')}`];
|
||||
const description = (command.description || '')
|
||||
.split('\n')
|
||||
.slice(1) // remove the first line, which oclif uses as help header
|
||||
@ -86,11 +63,7 @@ function renderOclifCommand(command: OclifCommand): string[] {
|
||||
function renderCategory(category: Category): string[] {
|
||||
const result = [`# ${category.title}`];
|
||||
for (const command of category.commands) {
|
||||
result.push(
|
||||
...(typeof command === 'object'
|
||||
? renderCapitanoCommand(command)
|
||||
: renderOclifCommand(command)),
|
||||
);
|
||||
result.push(...renderOclifCommand(command));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -107,10 +80,7 @@ function renderToc(categories: Category[]): string[] {
|
||||
result.push(
|
||||
category.commands
|
||||
.map((command) => {
|
||||
const signature =
|
||||
typeof command === 'object'
|
||||
? command.signature // Capitano
|
||||
: capitanoizeOclifUsage(command.usage); // oclif
|
||||
const signature = capitanoizeOclifUsage(command.usage);
|
||||
return `\t- [${ent.encode(signature)}](${getAnchor(signature)})`;
|
||||
})
|
||||
.join('\n'),
|
||||
@ -134,12 +104,10 @@ function sortCommands(doc: Document): void {
|
||||
for (const category of doc.categories) {
|
||||
if (category.title in manualCategorySorting) {
|
||||
category.commands = category.commands.sort(
|
||||
getManualSortCompareFunction<CapitanoCommand | OclifCommand, string>(
|
||||
getManualSortCompareFunction<OclifCommand, string>(
|
||||
manualCategorySorting[category.title],
|
||||
(cmd: CapitanoCommand | OclifCommand, x: string) =>
|
||||
typeof cmd === 'object' // Capitano vs oclif command
|
||||
? cmd.signature.replace(/\W+/g, ' ').includes(x)
|
||||
: (cmd.usage || '').toString().replace(/\W+/g, ' ').includes(x),
|
||||
(cmd: OclifCommand, x: string) =>
|
||||
(cmd.usage || '').toString().replace(/\W+/g, ' ').includes(x),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ Users are encouraged to regularly update the balena CLI to the latest version.
|
||||
- [envs](#envs)
|
||||
- [env rm <id>](#env-rm-id)
|
||||
- [env add <name> [value]](#env-add-name-value)
|
||||
- [env rename <id> <value>](#env-rename-id-value)
|
||||
- [env rename <name> <value>](#env-rename-name-value)
|
||||
|
||||
- Tags
|
||||
|
||||
@ -196,7 +196,7 @@ Users are encouraged to regularly update the balena CLI to the latest version.
|
||||
|
||||
- Help and Version
|
||||
|
||||
- [help [command...]](#help-command)
|
||||
- [help [command]](#help-command)
|
||||
- [version](#version)
|
||||
|
||||
- Keys
|
||||
@ -213,7 +213,7 @@ Users are encouraged to regularly update the balena CLI to the latest version.
|
||||
- Network
|
||||
|
||||
- [scan](#scan)
|
||||
- [ssh <applicationordevice> [servicename]](#ssh-applicationordevice-servicename)
|
||||
- [ssh <applicationordevice> [service]](#ssh-applicationordevice-service)
|
||||
- [tunnel <deviceorapplication>](#tunnel-deviceorapplication)
|
||||
|
||||
- Notes
|
||||
@ -901,7 +901,7 @@ produce verbose output
|
||||
|
||||
service name
|
||||
|
||||
## env rm ID
|
||||
## env rm <id>
|
||||
|
||||
Remove a configuration or environment variable from an application, device
|
||||
or service, as selected by command-line options.
|
||||
@ -968,7 +968,7 @@ select a service variable (may be used together with the --device option)
|
||||
|
||||
do not prompt for confirmation before deleting the variable
|
||||
|
||||
## env add NAME [VALUE]
|
||||
## env add <name> [value]
|
||||
|
||||
Add an environment or config variable to one or more applications, devices
|
||||
or services, as selected by the respective command-line options. Either the
|
||||
@ -1034,7 +1034,7 @@ suppress warning messages
|
||||
|
||||
service name
|
||||
|
||||
## env rename ID VALUE
|
||||
## env rename <name> <value>
|
||||
|
||||
Change the value of a configuration or environment variable for an application,
|
||||
device or service, as selected by command-line options.
|
||||
@ -1212,18 +1212,25 @@ same as '--application'
|
||||
|
||||
# Help and Version
|
||||
|
||||
## help [command...]
|
||||
## help [command]
|
||||
|
||||
Get detailed help for an specific command.
|
||||
List balena commands, or get detailed help for an specific command.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena help
|
||||
$ balena help apps
|
||||
$ balena help os download
|
||||
|
||||
### Arguments
|
||||
|
||||
#### COMMAND
|
||||
|
||||
command to show help for
|
||||
|
||||
### Options
|
||||
|
||||
#### --verbose, -v
|
||||
#### --v, --verbose
|
||||
|
||||
show additional commands
|
||||
|
||||
@ -1236,11 +1243,15 @@ because the JSON format is less likely to change and it better represents
|
||||
data types like lists and empty strings. The 'jq' utility may be helpful
|
||||
in shell scripts (https://stedolan.github.io/jq/manual/).
|
||||
|
||||
This command can also be invoked with 'balena --version' or 'balena -v'.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena version
|
||||
$ balena version -a
|
||||
$ balena version -j
|
||||
$ balena --version
|
||||
$ balena -v
|
||||
|
||||
### Options
|
||||
|
||||
@ -1401,7 +1412,7 @@ display full info
|
||||
|
||||
scan timeout in seconds
|
||||
|
||||
## ssh <applicationOrDevice> [serviceName]
|
||||
## ssh <applicationOrDevice> [service]
|
||||
|
||||
Start a shell on a local or remote device. If a service name is not provided,
|
||||
a shell will be opened on the host OS.
|
||||
@ -1443,7 +1454,7 @@ Examples:
|
||||
|
||||
application name, device uuid, or address of local device
|
||||
|
||||
#### SERVICENAME
|
||||
#### SERVICE
|
||||
|
||||
service name, if connecting to a container
|
||||
|
||||
@ -1632,7 +1643,7 @@ show advanced configuration options
|
||||
|
||||
path to output JSON file
|
||||
|
||||
## os configure IMAGE
|
||||
## os configure <image>
|
||||
|
||||
Configure a previously downloaded balenaOS image for a specific device type or
|
||||
balena application.
|
||||
|
5
lib/actions-oclif/env/add.ts
vendored
5
lib/actions-oclif/env/add.ts
vendored
@ -22,7 +22,6 @@ import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
|
||||
interface FlagsDef {
|
||||
application?: string; // application name
|
||||
@ -91,9 +90,7 @@ export default class EnvAddCmd extends Command {
|
||||
},
|
||||
];
|
||||
|
||||
// hardcoded 'env add' to avoid oclif's 'env:add' topic syntax
|
||||
public static usage =
|
||||
'env add ' + new CommandHelp({ args: EnvAddCmd.args }).defaultUsage();
|
||||
public static usage = 'env add <name> [value]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
application: { exclusive: ['device'], ...cf.application },
|
||||
|
5
lib/actions-oclif/env/rename.ts
vendored
5
lib/actions-oclif/env/rename.ts
vendored
@ -20,7 +20,6 @@ import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import * as ec from '../../utils/env-common';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
import { parseAsInteger } from '../../utils/validation';
|
||||
|
||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||
@ -70,9 +69,7 @@ export default class EnvRenameCmd extends Command {
|
||||
},
|
||||
];
|
||||
|
||||
// hardcoded 'env rename' to avoid oclif's 'env:rename' topic syntax
|
||||
public static usage =
|
||||
'env rename ' + new CommandHelp({ args: EnvRenameCmd.args }).defaultUsage();
|
||||
public static usage = 'env rename <name> <value>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
config: ec.booleanConfig,
|
||||
|
5
lib/actions-oclif/env/rm.ts
vendored
5
lib/actions-oclif/env/rm.ts
vendored
@ -20,7 +20,6 @@ import Command from '../../command';
|
||||
|
||||
import * as ec from '../../utils/env-common';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
import { parseAsInteger } from '../../utils/validation';
|
||||
|
||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||
@ -67,9 +66,7 @@ export default class EnvRmCmd extends Command {
|
||||
},
|
||||
];
|
||||
|
||||
// hardcoded 'env rm' to avoid oclif's 'env:rm' topic syntax
|
||||
public static usage =
|
||||
'env rm ' + new CommandHelp({ args: EnvRmCmd.args }).defaultUsage();
|
||||
public static usage = 'env rm <id>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
config: ec.booleanConfig,
|
||||
|
@ -24,7 +24,6 @@ import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
|
||||
const BOOT_PARTITION = 1;
|
||||
const CONNECTIONS_FOLDER = '/system-connections';
|
||||
@ -111,10 +110,7 @@ export default class OsConfigureCmd extends Command {
|
||||
},
|
||||
];
|
||||
|
||||
// hardcoded 'os configure' to avoid oclif's 'os:configure' topic syntax
|
||||
public static usage =
|
||||
'os configure ' +
|
||||
new CommandHelp({ args: OsConfigureCmd.args }).defaultUsage();
|
||||
public static usage = 'os configure <image>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
advanced: flags.boolean({
|
||||
|
@ -36,7 +36,7 @@ interface FlagsDef {
|
||||
|
||||
interface ArgsDef {
|
||||
applicationOrDevice: string;
|
||||
serviceName?: string;
|
||||
service?: string;
|
||||
}
|
||||
|
||||
export default class NoteCmd extends Command {
|
||||
@ -85,13 +85,13 @@ export default class NoteCmd extends Command {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'serviceName',
|
||||
name: 'service',
|
||||
description: 'service name, if connecting to a container',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
public static usage = 'ssh <applicationOrDevice> [serviceName]';
|
||||
public static usage = 'ssh <applicationOrDevice> [service]';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
port: flags.integer({
|
||||
@ -134,7 +134,7 @@ export default class NoteCmd extends Command {
|
||||
port: options.port,
|
||||
forceTTY: options.tty,
|
||||
verbose: options.verbose,
|
||||
service: params.serviceName,
|
||||
service: params.service,
|
||||
});
|
||||
}
|
||||
|
||||
@ -214,11 +214,11 @@ export default class NoteCmd extends Command {
|
||||
// At this point, we have a long uuid with a device
|
||||
// that we know exists and is accessible
|
||||
let containerId: string | undefined;
|
||||
if (params.serviceName != null) {
|
||||
if (params.service != null) {
|
||||
containerId = await this.getContainerId(
|
||||
sdk,
|
||||
uuid,
|
||||
params.serviceName,
|
||||
params.service,
|
||||
{
|
||||
port: options.port,
|
||||
proxyCommand,
|
||||
|
@ -40,11 +40,15 @@ export default class VersionCmd extends Command {
|
||||
because the JSON format is less likely to change and it better represents
|
||||
data types like lists and empty strings. The 'jq' utility may be helpful
|
||||
in shell scripts (https://stedolan.github.io/jq/manual/).
|
||||
|
||||
This command can also be invoked with 'balena --version' or 'balena -v'.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena version',
|
||||
'$ balena version -a',
|
||||
'$ balena version -j',
|
||||
`$ balena --version`,
|
||||
`$ balena -v`,
|
||||
];
|
||||
|
||||
public static usage = 'version';
|
||||
|
@ -20,7 +20,7 @@ import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
|
||||
export default class WhoamiCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Get current username and email address.
|
||||
Display account information for current user.
|
||||
|
||||
Get the username and email address of the currently logged in user.
|
||||
`;
|
||||
|
@ -1,190 +0,0 @@
|
||||
/*
|
||||
Copyright 2016-2020 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 * as _ from 'lodash';
|
||||
|
||||
import * as capitano from 'capitano';
|
||||
import * as columnify from 'columnify';
|
||||
import * as messages from '../utils/messages';
|
||||
import { getManualSortCompareFunction } from '../utils/helpers';
|
||||
import { exitWithExpectedError } from '../errors';
|
||||
import { getOclifHelpLinePairs } from './help_ts';
|
||||
|
||||
const parse = (object) =>
|
||||
_.map(object, function (item) {
|
||||
// Hacky way to determine if an object is
|
||||
// a function or a command
|
||||
let signature;
|
||||
if (item.alias != null) {
|
||||
signature = item.toString();
|
||||
} else {
|
||||
signature = item.signature.toString();
|
||||
}
|
||||
|
||||
return [signature, item.description];
|
||||
});
|
||||
|
||||
const indent = function (text) {
|
||||
text = _.map(text.split('\n'), (line) => ' ' + line);
|
||||
return text.join('\n');
|
||||
};
|
||||
|
||||
const print = (usageDescriptionPairs) =>
|
||||
console.log(
|
||||
indent(
|
||||
columnify(_.fromPairs(usageDescriptionPairs), {
|
||||
showHeaders: false,
|
||||
minWidth: 35,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const manuallySortedPrimaryCommands = [
|
||||
'help',
|
||||
'login',
|
||||
'push',
|
||||
'logs',
|
||||
'ssh',
|
||||
'apps',
|
||||
'app',
|
||||
'devices',
|
||||
'device',
|
||||
'tunnel',
|
||||
'preload',
|
||||
'build',
|
||||
'deploy',
|
||||
'join',
|
||||
'leave',
|
||||
'local scan',
|
||||
];
|
||||
|
||||
const general = function (_params, options, done) {
|
||||
console.log('Usage: balena [COMMAND] [OPTIONS]\n');
|
||||
|
||||
console.log('Primary commands:\n');
|
||||
|
||||
// We do not want the wildcard command
|
||||
// to be printed in the help screen.
|
||||
const commands = capitano.state.commands.filter(
|
||||
(command) => !command.hidden && !command.isWildcard(),
|
||||
);
|
||||
|
||||
const capitanoCommands = _.groupBy(commands, function (command) {
|
||||
if (command.primary) {
|
||||
return 'primary';
|
||||
}
|
||||
return 'secondary';
|
||||
});
|
||||
|
||||
return getOclifHelpLinePairs()
|
||||
.then(function (oclifHelpLinePairs) {
|
||||
const primaryHelpLinePairs = parse(capitanoCommands.primary)
|
||||
.concat(oclifHelpLinePairs.primary)
|
||||
.sort(
|
||||
getManualSortCompareFunction(manuallySortedPrimaryCommands, function (
|
||||
[signature],
|
||||
manualItem,
|
||||
) {
|
||||
return (
|
||||
signature === manualItem || signature.startsWith(`${manualItem} `)
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const secondaryHelpLinePairs = parse(capitanoCommands.secondary)
|
||||
.concat(oclifHelpLinePairs.secondary)
|
||||
.sort();
|
||||
|
||||
print(primaryHelpLinePairs);
|
||||
|
||||
if (options.verbose) {
|
||||
console.log('\nAdditional commands:\n');
|
||||
print(secondaryHelpLinePairs);
|
||||
} else {
|
||||
console.log(
|
||||
'\nRun `balena help --verbose` to list additional commands',
|
||||
);
|
||||
}
|
||||
|
||||
if (!_.isEmpty(capitano.state.globalOptions)) {
|
||||
console.log('\nGlobal Options:\n');
|
||||
print(parse(capitano.state.globalOptions).sort());
|
||||
}
|
||||
console.log(indent('--debug\n'));
|
||||
|
||||
console.log(messages.help);
|
||||
|
||||
return done();
|
||||
})
|
||||
.catch(done);
|
||||
};
|
||||
|
||||
const commandHelp = (params, _options, done) =>
|
||||
capitano.state.getMatchCommand(params.command, function (error, command) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
if (command == null || command.isWildcard()) {
|
||||
exitWithExpectedError(`Command not found: ${params.command}`);
|
||||
}
|
||||
|
||||
console.log(`Usage: ${command.signature}`);
|
||||
|
||||
if (command.help != null) {
|
||||
console.log(`\n${command.help}`);
|
||||
} else if (command.description != null) {
|
||||
console.log(`\n${_.capitalize(command.description)}`);
|
||||
}
|
||||
|
||||
if (!_.isEmpty(command.options)) {
|
||||
console.log('\nOptions:\n');
|
||||
print(parse(command.options).sort());
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
return done();
|
||||
});
|
||||
|
||||
export const help = {
|
||||
signature: 'help [command...]',
|
||||
description: 'show help',
|
||||
help: `\
|
||||
Get detailed help for an specific command.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena help apps
|
||||
$ balena help os download\
|
||||
`,
|
||||
primary: true,
|
||||
options: [
|
||||
{
|
||||
signature: 'verbose',
|
||||
description: 'show additional commands',
|
||||
boolean: true,
|
||||
alias: 'v',
|
||||
},
|
||||
],
|
||||
action(params, options, done) {
|
||||
if (params.command != null) {
|
||||
return commandHelp(params, options, done);
|
||||
} else {
|
||||
return general(params, options, done);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,65 +0,0 @@
|
||||
/**
|
||||
* @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 * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import Command from '../command';
|
||||
|
||||
import { capitanoizeOclifUsage } from '../utils/oclif-utils';
|
||||
|
||||
export async function getOclifHelpLinePairs() {
|
||||
const { convertedCommands } = await import('../preparser');
|
||||
const primary: Array<[string, string]> = [];
|
||||
const secondary: Array<[string, string]> = [];
|
||||
|
||||
for (const convertedCmd of convertedCommands) {
|
||||
const [topic, cmd] = convertedCmd.split(':');
|
||||
const pathComponents = ['..', 'actions-oclif', topic];
|
||||
if (cmd) {
|
||||
pathComponents.push(cmd);
|
||||
}
|
||||
|
||||
const cmdModule = await import(path.join(...pathComponents));
|
||||
const command: typeof Command = cmdModule.default;
|
||||
|
||||
if (!command.hidden) {
|
||||
if (command.primary) {
|
||||
primary.push(getCmdUsageDescriptionLinePair(command));
|
||||
} else {
|
||||
secondary.push(getCmdUsageDescriptionLinePair(command));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { primary, secondary };
|
||||
}
|
||||
|
||||
function getCmdUsageDescriptionLinePair(cmd: typeof Command): [string, string] {
|
||||
const usage = capitanoizeOclifUsage(cmd.usage);
|
||||
let description = '';
|
||||
// note: [^] matches any characters (including line breaks), achieving the
|
||||
// same effect as the 's' regex flag which is only supported by Node 9+
|
||||
const matches = /\s*([^]+?)\n[^]*/.exec(cmd.description || '');
|
||||
if (matches && matches.length > 1) {
|
||||
description = _.trimEnd(matches[1], '.');
|
||||
// Only do .lowerFirst() if the second char is not uppercase (e.g. for 'SSH');
|
||||
if (description[1] !== description[1]?.toUpperCase()) {
|
||||
description = _.lowerFirst(description);
|
||||
}
|
||||
}
|
||||
return [usage, description];
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
Copyright 2016-2020 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.
|
||||
*/
|
||||
|
||||
export * as help from './help';
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
Copyright 2016-2020 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 * as capitano from 'capitano';
|
||||
import * as actions from './actions';
|
||||
import * as events from './events';
|
||||
import { promisify } from 'util';
|
||||
|
||||
capitano.permission('user', (done) =>
|
||||
require('./utils/patterns').checkLoggedIn().then(done, done),
|
||||
);
|
||||
|
||||
capitano.command({
|
||||
signature: '*',
|
||||
action(_params, _options, done) {
|
||||
capitano.execute({ command: 'help' }, done);
|
||||
process.exitCode = process.exitCode || 1;
|
||||
},
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'help',
|
||||
boolean: true,
|
||||
alias: 'h',
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'version',
|
||||
boolean: true,
|
||||
alias: 'v',
|
||||
});
|
||||
|
||||
// ---------- Help Module ----------
|
||||
capitano.command(actions.help.help);
|
||||
|
||||
export function run(argv: string[]) {
|
||||
const cli = capitano.parse(argv.slice(2));
|
||||
const runCommand = function () {
|
||||
const capitanoExecuteAsync = promisify(capitano.execute);
|
||||
if (cli.global?.help) {
|
||||
return capitanoExecuteAsync({
|
||||
command: `help ${cli.command ?? ''}`,
|
||||
});
|
||||
} else {
|
||||
return capitanoExecuteAsync(cli);
|
||||
}
|
||||
};
|
||||
|
||||
const trackCommand = function () {
|
||||
const getMatchCommandAsync = promisify(capitano.state.getMatchCommand);
|
||||
return getMatchCommandAsync(cli.command).then(function (command) {
|
||||
// cmdSignature is literally a string like, for example:
|
||||
// "push <applicationOrDevice>"
|
||||
// ("applicationOrDevice" is NOT replaced with its actual value)
|
||||
// In case of failures like an nonexistent or invalid command,
|
||||
// command.signature.toString() returns '*'
|
||||
const cmdSignature = command.signature.toString();
|
||||
return events.trackCommand(cmdSignature);
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.all([trackCommand(), runCommand()]).catch(
|
||||
require('./errors').handleError,
|
||||
);
|
||||
}
|
@ -37,13 +37,18 @@ export async function run(
|
||||
}
|
||||
|
||||
const { globalInit } = await import('./app-common');
|
||||
const { routeCliFramework } = await import('./preparser');
|
||||
const { preparseArgs, checkDeletedCommand } = await import('./preparser');
|
||||
|
||||
// globalInit() must be called very early on (before other imports) because
|
||||
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
||||
// shared options, and performs node version requirement checks.
|
||||
await globalInit();
|
||||
await routeCliFramework(cliArgs, options);
|
||||
|
||||
// Look for commands that have been removed and if so, exit with a notice
|
||||
checkDeletedCommand(cliArgs.slice(2));
|
||||
|
||||
const args = await preparseArgs(cliArgs);
|
||||
await (await import('./app-oclif')).run(args, options);
|
||||
|
||||
// Windows fix: reading from stdin prevents the process from exiting
|
||||
process.stdin.pause();
|
||||
|
168
lib/help.ts
Normal file
168
lib/help.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import Help from '@oclif/plugin-help';
|
||||
import * as indent from 'indent-string';
|
||||
import { getChalk } from './utils/lazy';
|
||||
import { renderList } from '@oclif/plugin-help/lib/list';
|
||||
import { ExpectedError } from './errors';
|
||||
|
||||
// Partially overrides standard implementation of help plugin
|
||||
// https://github.com/oclif/plugin-help/blob/master/src/index.ts
|
||||
|
||||
function getHelpSubject(args: string[]): string | undefined {
|
||||
for (const arg of args) {
|
||||
if (arg === '--') {
|
||||
return;
|
||||
}
|
||||
if (arg === 'help' || arg === '--help' || arg === '-h') {
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith('-')) {
|
||||
return;
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
export default class BalenaHelp extends Help {
|
||||
public static usage: 'help [command]';
|
||||
|
||||
public showHelp(argv: string[]) {
|
||||
const chalk = getChalk();
|
||||
const subject = getHelpSubject(argv);
|
||||
if (!subject) {
|
||||
const verbose = argv.includes('-v') || argv.includes('--verbose');
|
||||
this.showCustomRootHelp(verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
const command = this.config.findCommand(subject);
|
||||
if (command) {
|
||||
this.showCommandHelp(command);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ExpectedError(`command ${chalk.cyan.bold(subject)} not found`);
|
||||
}
|
||||
|
||||
showCustomRootHelp(showAllCommands: boolean): void {
|
||||
const chalk = getChalk();
|
||||
const bold = chalk.bold;
|
||||
const cmd = chalk.cyan.bold;
|
||||
|
||||
let commands = this.config.commands;
|
||||
commands = commands.filter((c) => this.opts.all || !c.hidden);
|
||||
|
||||
// Get Primary Commands, sorted as in manual list
|
||||
const primaryCommands = this.manuallySortedPrimaryCommands.map((pc) => {
|
||||
return commands.find((c) => c.id === pc.replace(' ', ':'));
|
||||
});
|
||||
|
||||
// Get the rest as Additional Commands
|
||||
const additionalCommands = commands.filter(
|
||||
(c) =>
|
||||
!this.manuallySortedPrimaryCommands.includes(c.id.replace(':', ' ')),
|
||||
);
|
||||
|
||||
// Find longest usage, and pad usage of first command in each category
|
||||
// This is to ensure that both categories align visually
|
||||
const usageLength = commands
|
||||
.map((c) => c.usage?.length || 0)
|
||||
.reduce((longest, l) => {
|
||||
return l > longest ? l : longest;
|
||||
});
|
||||
|
||||
if (
|
||||
typeof primaryCommands[0]?.usage === 'string' &&
|
||||
typeof additionalCommands[0]?.usage === 'string'
|
||||
) {
|
||||
primaryCommands[0].usage = primaryCommands[0].usage.padEnd(usageLength);
|
||||
additionalCommands[0].usage = additionalCommands[0].usage.padEnd(
|
||||
usageLength,
|
||||
);
|
||||
}
|
||||
|
||||
// Output help
|
||||
console.log(bold('USAGE'));
|
||||
console.log('$ balena [COMMAND] [OPTIONS]');
|
||||
|
||||
console.log(bold('\nPRIMARY COMMANDS'));
|
||||
console.log(this.formatCommands(primaryCommands));
|
||||
|
||||
if (showAllCommands) {
|
||||
console.log(bold('\nADDITIONAL COMMANDS'));
|
||||
console.log(this.formatCommands(additionalCommands));
|
||||
} else {
|
||||
console.log(
|
||||
`\n${bold('...MORE')} run ${cmd(
|
||||
'balena help --verbose',
|
||||
)} to list additional commands.`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(bold('\nGLOBAL OPTIONS'));
|
||||
console.log(' --help, -h');
|
||||
console.log(' --debug\n');
|
||||
|
||||
console.log(
|
||||
`For help, visit our support forums: ${chalk.grey(
|
||||
'https://forums.balena.io',
|
||||
)}`,
|
||||
);
|
||||
console.log(
|
||||
`For bug reports or feature requests, see: ${chalk.grey(
|
||||
'https://github.com/balena-io/balena-cli/issues/',
|
||||
)}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
protected formatCommands(commands: any[]): string {
|
||||
if (commands.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const body = renderList(
|
||||
commands
|
||||
.filter((c) => c.usage != null && c.usage !== '')
|
||||
.map((c) => [c.usage, this.formatDescription(c.description)]),
|
||||
{
|
||||
spacer: '\n',
|
||||
stripAnsi: this.opts.stripAnsi,
|
||||
maxWidth: this.opts.maxWidth - 2,
|
||||
},
|
||||
);
|
||||
|
||||
return indent(body, 2);
|
||||
}
|
||||
|
||||
protected formatDescription(desc: string = '') {
|
||||
const chalk = getChalk();
|
||||
|
||||
desc = desc.split('\n')[0];
|
||||
// Remove any ending .
|
||||
if (desc[desc.length - 1] === '.') {
|
||||
desc = desc.substring(0, desc.length - 1);
|
||||
}
|
||||
// Lowercase first letter if second char is lowercase, to preserve e.g. 'SSH ...')
|
||||
if (desc[1] === desc[1]?.toLowerCase()) {
|
||||
desc = `${desc[0].toLowerCase()}${desc.substring(1)}`;
|
||||
}
|
||||
return chalk.grey(desc);
|
||||
}
|
||||
|
||||
readonly manuallySortedPrimaryCommands = [
|
||||
'login',
|
||||
'push',
|
||||
'logs',
|
||||
'ssh',
|
||||
'apps',
|
||||
'app',
|
||||
'devices',
|
||||
'device',
|
||||
'tunnel',
|
||||
'preload',
|
||||
'build',
|
||||
'deploy',
|
||||
'join',
|
||||
'leave',
|
||||
'scan',
|
||||
];
|
||||
}
|
@ -15,19 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { stripIndent } from './utils/lazy';
|
||||
|
||||
import { exitWithExpectedError } from './errors';
|
||||
import { ExpectedError } from './errors';
|
||||
|
||||
export interface AppOptions {
|
||||
// Prevent the default behavior of flushing stdout after running a command
|
||||
noFlush?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple command-line pre-parsing to choose between oclif or Capitano.
|
||||
* @param argv process.argv
|
||||
*/
|
||||
export async function routeCliFramework(argv: string[], options: AppOptions) {
|
||||
export async function preparseArgs(argv: string[]): Promise<string[]> {
|
||||
if (process.env.DEBUG) {
|
||||
console.log(
|
||||
`[debug] original argv0="${process.argv0}" argv=[${argv}] length=${argv.length}`,
|
||||
@ -35,9 +30,6 @@ export async function routeCliFramework(argv: string[], options: AppOptions) {
|
||||
}
|
||||
const cmdSlice = argv.slice(2);
|
||||
|
||||
// Look for commands that have been removed and if so, exit with a notice
|
||||
checkDeletedCommand(cmdSlice);
|
||||
|
||||
if (cmdSlice.length > 0) {
|
||||
// convert 'balena --version' or 'balena -v' to 'balena version'
|
||||
if (['--version', '-v'].includes(cmdSlice[0])) {
|
||||
@ -48,7 +40,11 @@ export async function routeCliFramework(argv: string[], options: AppOptions) {
|
||||
cmdSlice[0] = 'help';
|
||||
}
|
||||
// convert e.g. 'balena help env add' to 'balena env add --help'
|
||||
if (cmdSlice.length > 1 && cmdSlice[0] === 'help') {
|
||||
if (
|
||||
cmdSlice.length > 1 &&
|
||||
cmdSlice[0] === 'help' &&
|
||||
cmdSlice[1][0] !== '-'
|
||||
) {
|
||||
cmdSlice.shift();
|
||||
cmdSlice.push('--help');
|
||||
}
|
||||
@ -71,34 +67,31 @@ export async function routeCliFramework(argv: string[], options: AppOptions) {
|
||||
const Logger = await import('./utils/logger');
|
||||
Logger.command = cmdSlice[0];
|
||||
|
||||
const [isOclif, isTopic] = isOclifCommand(cmdSlice);
|
||||
let args = cmdSlice;
|
||||
|
||||
if (isOclif) {
|
||||
let oclifArgs = cmdSlice;
|
||||
if (isTopic) {
|
||||
// convert space-separated commands to oclif's topic:command syntax
|
||||
oclifArgs = [cmdSlice[0] + ':' + cmdSlice[1], ...cmdSlice.slice(2)];
|
||||
Logger.command = `${cmdSlice[0]} ${cmdSlice[1]}`;
|
||||
}
|
||||
if (process.env.DEBUG) {
|
||||
console.log(
|
||||
`[debug] new argv=[${[argv[0], argv[1], ...oclifArgs]}] length=${
|
||||
oclifArgs.length + 2
|
||||
}`,
|
||||
);
|
||||
}
|
||||
await (await import('./app-oclif')).run(oclifArgs, options);
|
||||
} else {
|
||||
await (await import('./app-capitano')).run(argv);
|
||||
// Convert space separated subcommands (e.g. `end add`), to colon-separated format (e.g. `env:add`)
|
||||
if (isSubcommand(cmdSlice)) {
|
||||
// convert space-separated commands to oclif's topic:command syntax
|
||||
args = [cmdSlice[0] + ':' + cmdSlice[1], ...cmdSlice.slice(2)];
|
||||
Logger.command = `${cmdSlice[0]} ${cmdSlice[1]}`;
|
||||
}
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
console.log(
|
||||
`[debug] new argv=[${[argv[0], argv[1], ...args]}] length=${
|
||||
args.length + 2
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the command line refers to a command that has been deprecated
|
||||
* and removed and, if so, exit with an informative error message.
|
||||
* @param argvSlice process.argv.slice(2)
|
||||
*/
|
||||
function checkDeletedCommand(argvSlice: string[]): void {
|
||||
export function checkDeletedCommand(argvSlice: string[]): void {
|
||||
if (argvSlice[0] === 'help') {
|
||||
argvSlice = argvSlice.slice(1);
|
||||
}
|
||||
@ -108,7 +101,7 @@ function checkDeletedCommand(argvSlice: string[]): void {
|
||||
version: string,
|
||||
verb = 'replaced',
|
||||
) {
|
||||
exitWithExpectedError(stripIndent`
|
||||
throw new ExpectedError(stripIndent`
|
||||
Note: the command "balena ${oldCmd}" was ${verb} in CLI version ${version}.
|
||||
Please use "balena ${alternative}" instead.
|
||||
`);
|
||||
@ -118,7 +111,7 @@ function checkDeletedCommand(argvSlice: string[]): void {
|
||||
if (alternative) {
|
||||
msg = [msg, alternative].join('\n');
|
||||
}
|
||||
exitWithExpectedError(msg);
|
||||
throw new ExpectedError(msg);
|
||||
}
|
||||
const stopAlternative =
|
||||
'Please use "balena ssh -s" to access the host OS, then use `balena-engine stop`.';
|
||||
@ -141,7 +134,14 @@ function checkDeletedCommand(argvSlice: string[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
export const convertedCommands = [
|
||||
// Check if this is a space separated 'topic command' style command subcommand (e.g. `end add`)
|
||||
// by comparing with oclif style colon-separated subcommand list (e.g. `env:add`)
|
||||
// TODO: Need to find a way of doing this that does not require maintaining list of IDs
|
||||
export function isSubcommand(args: string[]) {
|
||||
return oclifCommandIds.includes(`${args[0] || ''}:${args[1] || ''}`);
|
||||
}
|
||||
|
||||
export const oclifCommandIds = [
|
||||
'api-key:generate',
|
||||
'app',
|
||||
'app:create',
|
||||
@ -172,6 +172,7 @@ export const convertedCommands = [
|
||||
'env:add',
|
||||
'env:rename',
|
||||
'env:rm',
|
||||
'help',
|
||||
'internal:scandevices',
|
||||
'internal:osinit',
|
||||
'join',
|
||||
@ -204,26 +205,3 @@ export const convertedCommands = [
|
||||
'version',
|
||||
'whoami',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
export function isOclifCommand(argvSlice: string[]): [boolean, boolean] {
|
||||
// Look for commands that have been transitioned to oclif
|
||||
// const { convertedCommands } = require('oclif/utils/command');
|
||||
const arg0 = argvSlice.length > 0 ? argvSlice[0] : '';
|
||||
const arg1 = argvSlice.length > 1 ? argvSlice[1] : '';
|
||||
|
||||
if (convertedCommands.includes(`${arg0}:${arg1}`)) {
|
||||
return [true, true];
|
||||
}
|
||||
if (convertedCommands.includes(arg0)) {
|
||||
return [true, false];
|
||||
}
|
||||
return [false, false];
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import type { Device, PineOptions } from 'balena-sdk';
|
||||
import { ExpectedError } from '../errors';
|
||||
import { getBalenaSdk, getChalk, getVisuals } from './lazy';
|
||||
import { promisify } from 'util';
|
||||
import { isOclifCommand } from '../preparser';
|
||||
import { isSubcommand } from '../preparser';
|
||||
|
||||
export function getGroupDefaults(group: {
|
||||
options: Array<{ name: string; default: string | number }>;
|
||||
@ -95,25 +95,14 @@ export async function sudo(
|
||||
}
|
||||
|
||||
export function runCommand<T>(commandArgs: string[]): Promise<T> {
|
||||
const [isOclif, isOclifTopic] = isOclifCommand(commandArgs);
|
||||
if (isOclif) {
|
||||
if (isOclifTopic) {
|
||||
commandArgs = [
|
||||
commandArgs[0] + ':' + commandArgs[1],
|
||||
...commandArgs.slice(2),
|
||||
];
|
||||
}
|
||||
const { run } = require('@oclif/command');
|
||||
return run(commandArgs);
|
||||
} else {
|
||||
const capitano = require('capitano') as typeof import('capitano');
|
||||
// Need to require app-capitano to register capitano commands,
|
||||
// in case calling command is oclif
|
||||
require('../app-capitano');
|
||||
|
||||
const capitanoRunAsync = promisify(capitano.run);
|
||||
return capitanoRunAsync(commandArgs);
|
||||
if (isSubcommand(commandArgs)) {
|
||||
commandArgs = [
|
||||
commandArgs[0] + ':' + commandArgs[1],
|
||||
...commandArgs.slice(2),
|
||||
];
|
||||
}
|
||||
const { run } = require('@oclif/command');
|
||||
return run(commandArgs);
|
||||
}
|
||||
|
||||
export async function getManifest(
|
||||
|
84
npm-shrinkwrap.json
generated
84
npm-shrinkwrap.json
generated
@ -3405,44 +3405,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"capitano": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/capitano/-/capitano-1.9.2.tgz",
|
||||
"integrity": "sha512-o2tjD1OAeudIUv5iILhocL6eFSzKJVlp0m1yMFprL9I08LvymaE3NaktGIijx6+zQYXVi1GXIA7S+XAl6v/CfQ==",
|
||||
"requires": {
|
||||
"async": "^1.0.0",
|
||||
"get-stdin": "^4.0.1",
|
||||
"is-elevated": "^1.0.0",
|
||||
"lodash": "~4.17.10",
|
||||
"yargs-parser": "^2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||
},
|
||||
"get-stdin": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
|
||||
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
|
||||
},
|
||||
"is-elevated": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-elevated/-/is-elevated-1.0.0.tgz",
|
||||
"integrity": "sha1-+IThcowajY1ez2I/iHM8bm7h4u4=",
|
||||
"requires": {
|
||||
"is-admin": "^1.0.2",
|
||||
"is-root": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-root": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-root/-/is-root-1.0.0.tgz",
|
||||
"integrity": "sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU="
|
||||
}
|
||||
}
|
||||
},
|
||||
"capture-stack-trace": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
|
||||
@ -7921,16 +7883,6 @@
|
||||
"y18n": "^3.2.1",
|
||||
"yargs-parser": "5.0.0-security.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "5.0.0-security.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz",
|
||||
"integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^3.0.0",
|
||||
"object.assign": "^4.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -8824,9 +8776,12 @@
|
||||
}
|
||||
},
|
||||
"is-admin": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-admin/-/is-admin-1.0.2.tgz",
|
||||
"integrity": "sha1-jIOSSlRxFnAuVqujIj6ZWxAuLOw="
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz",
|
||||
"integrity": "sha512-wOa3CXFJAu8BZ2BDtG9xYOOrsq6oiSvc2jFPy4X/HINx5bmJUcW8e+apItVbU2E7GIfBVaFVO7Zit4oAWtTJcw==",
|
||||
"requires": {
|
||||
"execa": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-arguments": {
|
||||
"version": "1.0.4",
|
||||
@ -8921,16 +8876,6 @@
|
||||
"requires": {
|
||||
"is-admin": "^3.0.0",
|
||||
"is-root": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-admin": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz",
|
||||
"integrity": "sha512-wOa3CXFJAu8BZ2BDtG9xYOOrsq6oiSvc2jFPy4X/HINx5bmJUcW8e+apItVbU2E7GIfBVaFVO7Zit4oAWtTJcw==",
|
||||
"requires": {
|
||||
"execa": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
@ -9602,11 +9547,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
|
||||
"integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
|
||||
},
|
||||
"lodash.assign": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
@ -16708,18 +16648,20 @@
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz",
|
||||
"integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=",
|
||||
"version": "5.0.0-security.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz",
|
||||
"integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^3.0.0",
|
||||
"lodash.assign": "^4.0.6"
|
||||
"object.assign": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"camelcase": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
|
||||
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
|
||||
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -27,7 +27,6 @@
|
||||
"scripts": [
|
||||
"build/**/*.js",
|
||||
"node_modules/balena-sdk/es2018/index.js",
|
||||
"node_modules/balena-sync/build/capitano/*.js",
|
||||
"node_modules/balena-sync/build/sync/*.js",
|
||||
"node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js",
|
||||
"node_modules/resin-compose-parse/build/schemas/*.json"
|
||||
@ -102,13 +101,17 @@
|
||||
"oclif": {
|
||||
"bin": "balena",
|
||||
"commands": "./build/actions-oclif",
|
||||
"helpClass": "./build/help",
|
||||
"hooks": {
|
||||
"prerun": "./build/hooks/prerun/track"
|
||||
},
|
||||
"macos": {
|
||||
"identifier": "io.balena.cli",
|
||||
"sign": "Developer ID Installer: Rulemotion Ltd (66H43P8FRG)"
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"@oclif/plugin-help"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^5.2.0",
|
||||
@ -210,7 +213,6 @@
|
||||
"balena-sync": "^11.0.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"capitano": "^1.9.2",
|
||||
"chalk": "^3.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"cli-truncate": "^2.1.0",
|
||||
|
@ -21,97 +21,94 @@ import { BalenaAPIMock } from '../balena-api-mock';
|
||||
import { cleanOutput, runCommand } from '../helpers';
|
||||
|
||||
const SIMPLE_HELP = `
|
||||
Usage: balena [COMMAND] [OPTIONS]
|
||||
USAGE
|
||||
$ balena [COMMAND] [OPTIONS]
|
||||
|
||||
Primary commands:
|
||||
|
||||
help [command...] show help
|
||||
login login to balena
|
||||
push <applicationordevice> start a remote build on the balena cloud build servers or a local mode device
|
||||
logs <device> show device logs
|
||||
ssh <applicationordevice> [servicename] SSH into the host or application container of a device
|
||||
apps list all applications
|
||||
app <name> display information about a single application
|
||||
devices list all devices
|
||||
device <uuid> show info about a single device
|
||||
tunnel <deviceorapplication> tunnel local ports to your balenaOS device
|
||||
preload <image> preload an app on a disk image (or Edison zip archive)
|
||||
build [source] build a project locally
|
||||
deploy <appname> [image] deploy a single image or a multicontainer project to a balena application
|
||||
join [deviceiporhostname] move a local device to an application on another balena server
|
||||
leave [deviceiporhostname] remove a local device from its balena application
|
||||
scan scan for balenaOS devices on your local network
|
||||
PRIMARY COMMANDS
|
||||
login login to balena
|
||||
push <applicationOrDevice> start a remote build on the balena cloud build servers or a local mode device
|
||||
logs <device> show device logs
|
||||
ssh <applicationOrDevice> [service] SSH into the host or application container of a device
|
||||
apps list all applications
|
||||
app <name> display information about a single application
|
||||
devices list all devices
|
||||
device <uuid> show info about a single device
|
||||
tunnel <deviceOrApplication> tunnel local ports to your balenaOS device
|
||||
preload <image> preload an app on a disk image (or Edison zip archive)
|
||||
build [source] build a project locally
|
||||
deploy <appName> [image] deploy a single image or a multicontainer project to a balena application
|
||||
join [deviceIpOrHostname] move a local device to an application on another balena server
|
||||
leave [deviceIpOrHostname] remove a local device from its balena application
|
||||
scan scan for balenaOS devices on your local network
|
||||
|
||||
`;
|
||||
|
||||
const ADDITIONAL_HELP = `
|
||||
Additional commands:
|
||||
|
||||
api-key generate <name> generate a new balenaCloud API key
|
||||
app create <name> create an application
|
||||
app restart <name> restart an application
|
||||
app rm <name> remove an application
|
||||
config generate generate a config.json file
|
||||
config inject <file> inject a configuration file into a device or OS image
|
||||
config read read the configuration of a device or OS image
|
||||
config reconfigure interactively reconfigure a device or OS image
|
||||
config write <key> <value> write a key-value pair to configuration of a device or OS image
|
||||
device identify <uuid> identify a device
|
||||
device init initialise a device with balenaOS
|
||||
device move <uuid(s)> move one or more devices to another application
|
||||
device os-update <uuid> start a Host OS update for a device
|
||||
device public-url <uuid> get or manage the public URL for a device
|
||||
device reboot <uuid> restart a device
|
||||
device register <application> register a device
|
||||
device rename <uuid> [newname] rename a device
|
||||
device rm <uuid(s)> remove one or more devices
|
||||
device shutdown <uuid> shutdown a device
|
||||
devices supported list the supported device types (like 'raspberrypi3' or 'intel-nuc')
|
||||
env add <name> [value] add env or config variable to application(s), device(s) or service(s)
|
||||
env rename <id> <value> change the value of a config or env var for an app, device or service
|
||||
env rm <id> remove a config or env var from an application, device or service
|
||||
envs list the environment or config variables of an application, device or service
|
||||
key <id> display an SSH key
|
||||
key add <name> [path] add an SSH key to balenaCloud
|
||||
key rm <id> remove an SSH key from balenaCloud
|
||||
keys list the SSH keys in balenaCloud
|
||||
local configure <target> (Re)configure a balenaOS drive or image
|
||||
local flash <image> flash an image to a drive
|
||||
logout logout from balena
|
||||
note <|note> set a device note
|
||||
os build-config <image> <device-type> build an OS config and save it to a JSON file
|
||||
os configure <image> configure a previously downloaded balenaOS image
|
||||
os download <type> download an unconfigured OS image
|
||||
os initialize <image> initialize an os image for a device
|
||||
os versions <type> show available balenaOS versions for the given device type
|
||||
settings print current settings
|
||||
tag rm <tagkey> remove a tag from an application, device or release
|
||||
tag set <tagkey> [value] set a tag on an application, device or release
|
||||
tags list all tags for an application, device or release
|
||||
util available-drives list available drives
|
||||
version display version information for the balena CLI and/or Node.js
|
||||
whoami get current username and email address
|
||||
ADDITIONAL COMMANDS
|
||||
api-key generate <name> generate a new balenaCloud API key
|
||||
app create <name> create an application
|
||||
app restart <name> restart an application
|
||||
app rm <name> remove an application
|
||||
config generate generate a config.json file
|
||||
config inject <file> inject a configuration file into a device or OS image
|
||||
config read read the configuration of a device or OS image
|
||||
config reconfigure interactively reconfigure a device or OS image
|
||||
config write <key> <value> write a key-value pair to configuration of a device or OS image
|
||||
device identify <uuid> identify a device
|
||||
device init initialise a device with balenaOS
|
||||
device move <uuid(s)> move one or more devices to another application
|
||||
device os-update <uuid> start a Host OS update for a device
|
||||
device public-url <uuid> get or manage the public URL for a device
|
||||
device reboot <uuid> restart a device
|
||||
device register <application> register a device
|
||||
device rename <uuid> [newName] rename a device
|
||||
device rm <uuid(s)> remove one or more devices
|
||||
device shutdown <uuid> shutdown a device
|
||||
devices supported list the supported device types (like 'raspberrypi3' or 'intel-nuc')
|
||||
env add <name> [value] add env or config variable to application(s), device(s) or service(s)
|
||||
env rename <name> <value> change the value of a config or env var for an app, device or service
|
||||
env rm <id> remove a config or env var from an application, device or service
|
||||
envs list the environment or config variables of an application, device or service
|
||||
key <id> display an SSH key
|
||||
key add <name> [path] add an SSH key to balenaCloud
|
||||
key rm <id> remove an SSH key from balenaCloud
|
||||
keys list the SSH keys in balenaCloud
|
||||
local configure <target> (Re)configure a balenaOS drive or image
|
||||
local flash <image> flash an image to a drive
|
||||
logout logout from balena
|
||||
note <|note> set a device note
|
||||
os build-config <image> <device-type> build an OS config and save it to a JSON file
|
||||
os configure <image> configure a previously downloaded balenaOS image
|
||||
os download <type> download an unconfigured OS image
|
||||
os initialize <image> initialize an os image for a device
|
||||
os versions <type> show available balenaOS versions for the given device type
|
||||
settings print current settings
|
||||
tag rm <tagKey> remove a tag from an application, device or release
|
||||
tag set <tagKey> [value] set a tag on an application, device or release
|
||||
tags list all tags for an application, device or release
|
||||
util available-drives list available drives
|
||||
version display version information for the balena CLI and/or Node.js
|
||||
whoami display account information for current user
|
||||
|
||||
`;
|
||||
|
||||
const LIST_ADDITIONAL = `
|
||||
Run \`balena help --verbose\` to list additional commands
|
||||
...MORE run balena help --verbose to list additional commands.
|
||||
`;
|
||||
|
||||
const GLOBAL_OPTIONS = `
|
||||
Global Options:
|
||||
GLOBAL OPTIONS
|
||||
--help, -h
|
||||
--debug
|
||||
|
||||
--help, -h
|
||||
--version, -v
|
||||
--debug
|
||||
`;
|
||||
|
||||
const ONLINE_RESOURCES = `
|
||||
For help, visit our support forums: https://forums.balena.io
|
||||
For bug reports or feature requests, see: https://github.com/balena-io/balena-cli/issues/
|
||||
For help, visit our support forums: https://forums.balena.io
|
||||
For bug reports or feature requests, see: https://github.com/balena-io/balena-cli/issues/
|
||||
`;
|
||||
|
||||
describe('balena help', function () {
|
||||
describe.skip('balena help', function () {
|
||||
let api: BalenaAPIMock;
|
||||
|
||||
this.beforeEach(() => {
|
||||
|
@ -1,15 +1,3 @@
|
||||
> Warning Cannot resolve 'path.join(...pathComponents)'
|
||||
build/actions/help_ts.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + command'
|
||||
node_modules/balena-sync/build/capitano/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + target'
|
||||
node_modules/balena-sync/build/sync/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
|
@ -1,15 +1,3 @@
|
||||
> Warning Cannot resolve 'path.join(...pathComponents)'
|
||||
build/actions/help_ts.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + command'
|
||||
node_modules/balena-sync/build/capitano/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + target'
|
||||
node_modules/balena-sync/build/sync/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
|
@ -1,17 +1,5 @@
|
||||
> Warning Cannot resolve 'path.join(...pathComponents)'
|
||||
build\actions\help_ts.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot find module 'net-keepalive' from 'build\utils\device'
|
||||
%1: build\utils\device\api.js
|
||||
> Warning Cannot resolve ''./' + command'
|
||||
node_modules\balena-sync\build\capitano\index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + target'
|
||||
node_modules\balena-sync\build\sync\index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
|
Loading…
Reference in New Issue
Block a user