mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-18 07:48:09 +00:00
Refactor oclif integration and preparser
Change-type: patch Signed-off-by: Paulo Castro <paulo@balena.io>
This commit is contained in:
23
lib/actions-oclif/env/add.ts
vendored
23
lib/actions-oclif/env/add.ts
vendored
@ -17,7 +17,9 @@
|
|||||||
|
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import * as cf from '../../utils/common-flags';
|
||||||
import { CommandHelp } from '../../utils/oclif-utils';
|
import { CommandHelp } from '../../utils/oclif-utils';
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
@ -71,22 +73,10 @@ export default class EnvAddCmd extends Command {
|
|||||||
'env add ' + new CommandHelp({ args: EnvAddCmd.args }).defaultUsage();
|
'env add ' + new CommandHelp({ args: EnvAddCmd.args }).defaultUsage();
|
||||||
|
|
||||||
public static flags: flags.Input<FlagsDef> = {
|
public static flags: flags.Input<FlagsDef> = {
|
||||||
application: flags.string({
|
application: _.assign({ exclusive: ['device'] }, cf.application),
|
||||||
char: 'a',
|
device: _.assign({ exclusive: ['application'] }, cf.device),
|
||||||
description: 'application name',
|
help: cf.help,
|
||||||
exclusive: ['device'],
|
quiet: cf.quiet,
|
||||||
}),
|
|
||||||
device: flags.string({
|
|
||||||
char: 'd',
|
|
||||||
description: 'device UUID',
|
|
||||||
exclusive: ['application'],
|
|
||||||
}),
|
|
||||||
help: flags.help({ char: 'h' }),
|
|
||||||
quiet: flags.boolean({
|
|
||||||
char: 'q',
|
|
||||||
description: 'suppress warning messages',
|
|
||||||
default: false,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public async run() {
|
public async run() {
|
||||||
@ -94,7 +84,6 @@ export default class EnvAddCmd extends Command {
|
|||||||
EnvAddCmd,
|
EnvAddCmd,
|
||||||
);
|
);
|
||||||
const Bluebird = await import('bluebird');
|
const Bluebird = await import('bluebird');
|
||||||
const _ = await import('lodash');
|
|
||||||
const balena = (await import('balena-sdk')).fromSharedOptions();
|
const balena = (await import('balena-sdk')).fromSharedOptions();
|
||||||
const { exitWithExpectedError } = await import('../../utils/patterns');
|
const { exitWithExpectedError } = await import('../../utils/patterns');
|
||||||
|
|
||||||
|
@ -90,15 +90,20 @@ general = (params, options, done) ->
|
|||||||
|
|
||||||
if options.verbose
|
if options.verbose
|
||||||
console.log('\nAdditional commands:\n')
|
console.log('\nAdditional commands:\n')
|
||||||
print parse(groupedCommands.secondary).concat(getOclifHelpLinePairs()).sort()
|
secondaryCommandPromise = getOclifHelpLinePairs()
|
||||||
|
.then (oclifHelpLinePairs) ->
|
||||||
|
print parse(groupedCommands.secondary).concat(oclifHelpLinePairs).sort()
|
||||||
else
|
else
|
||||||
console.log('\nRun `balena help --verbose` to list additional commands')
|
console.log('\nRun `balena help --verbose` to list additional commands')
|
||||||
|
secondaryCommandPromise = Promise.resolve()
|
||||||
|
|
||||||
|
secondaryCommandPromise
|
||||||
|
.then ->
|
||||||
if not _.isEmpty(capitano.state.globalOptions)
|
if not _.isEmpty(capitano.state.globalOptions)
|
||||||
console.log('\nGlobal Options:\n')
|
console.log('\nGlobal Options:\n')
|
||||||
print parse(capitano.state.globalOptions).sort()
|
print parse(capitano.state.globalOptions).sort()
|
||||||
|
done()
|
||||||
return done()
|
.catch(done)
|
||||||
|
|
||||||
command = (params, options, done) ->
|
command = (params, options, done) ->
|
||||||
capitano.state.getMatchCommand params.command, (error, command) ->
|
capitano.state.getMatchCommand params.command, (error, command) ->
|
||||||
|
@ -16,20 +16,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Command } from '@oclif/command';
|
import { Command } from '@oclif/command';
|
||||||
|
import * as Bluebird from 'bluebird';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
export function getOclifHelpLinePairs(): Array<[string, string]> {
|
export async function getOclifHelpLinePairs(): Promise<
|
||||||
// Although it's tempting to have these oclif commands 'require'd in a
|
Array<[string, string]>
|
||||||
// central place, it would impact on performance (CLI start time). An
|
> {
|
||||||
// improvement would probably be to automatically scan the actions-oclif
|
const { convertedCommands } = await import('../preparser');
|
||||||
// folder.
|
const cmdClasses: Array<Promise<typeof Command>> = [];
|
||||||
const EnvAddCmd = require('../actions-oclif/env/add').default;
|
for (const convertedCmd of convertedCommands) {
|
||||||
const EnvRmCmd = require('../actions-oclif/env/rm').default;
|
const [topic, cmd] = convertedCmd.split(':');
|
||||||
const VersionCmd = require('../actions-oclif/version').default;
|
const pathComponents = ['..', 'actions-oclif', topic];
|
||||||
return [EnvAddCmd, EnvRmCmd, VersionCmd].map(getCmdUsageDescriptionLinePair);
|
if (cmd) {
|
||||||
|
pathComponents.push(cmd);
|
||||||
|
}
|
||||||
|
// note that `import(path)` returns a promise
|
||||||
|
cmdClasses.push(import(path.join(...pathComponents)));
|
||||||
|
}
|
||||||
|
return Bluebird.map(cmdClasses, getCmdUsageDescriptionLinePair);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCmdUsageDescriptionLinePair(cmd: typeof Command): [string, string] {
|
function getCmdUsageDescriptionLinePair(cmdModule: any): [string, string] {
|
||||||
|
const cmd: typeof Command = cmdModule.default;
|
||||||
const usage = (cmd.usage || '').toString().toLowerCase();
|
const usage = (cmd.usage || '').toString().toLowerCase();
|
||||||
let description = '';
|
let description = '';
|
||||||
// note: [^] matches any characters (including line breaks), achieving the
|
// note: [^] matches any characters (including line breaks), achieving the
|
||||||
|
18
lib/app-capitano.d.ts
vendored
Normal file
18
lib/app-capitano.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function run(argv: string[]);
|
@ -18,7 +18,6 @@
|
|||||||
import { Main } from '@oclif/command';
|
import { Main } from '@oclif/command';
|
||||||
import { ExitError } from '@oclif/errors';
|
import { ExitError } from '@oclif/errors';
|
||||||
|
|
||||||
import { AppOptions } from './app';
|
|
||||||
import { trackPromise } from './hooks/prerun/track';
|
import { trackPromise } from './hooks/prerun/track';
|
||||||
|
|
||||||
class CustomMain extends Main {
|
class CustomMain extends Main {
|
||||||
@ -32,10 +31,12 @@ class CustomMain extends Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppOptions = import('./preparser').AppOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* oclif CLI entrypoint
|
* oclif CLI entrypoint
|
||||||
*/
|
*/
|
||||||
export function run(command: string[], options: AppOptions) {
|
export async function run(command: string[], options: AppOptions) {
|
||||||
const runPromise = CustomMain.run(command).then(
|
const runPromise = CustomMain.run(command).then(
|
||||||
() => {
|
() => {
|
||||||
if (!options.noFlush) {
|
if (!options.noFlush) {
|
||||||
@ -51,7 +52,9 @@ export function run(command: string[], options: AppOptions) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return Promise.all([trackPromise, runPromise]).catch(
|
try {
|
||||||
require('./errors').handleError,
|
await Promise.all([trackPromise, runPromise]);
|
||||||
);
|
} catch (err) {
|
||||||
|
await (await import('./errors')).handleError(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
148
lib/app.ts
148
lib/app.ts
@ -14,156 +14,18 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { stripIndent } from 'common-tags';
|
|
||||||
|
|
||||||
import { exitWithExpectedError } from './utils/patterns';
|
import { globalInit } from './app-common';
|
||||||
|
import { AppOptions, routeCliFramework } from './preparser';
|
||||||
export interface AppOptions {
|
|
||||||
// Prevent the default behaviour of flushing stdout after running a command
|
|
||||||
noFlush?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple command-line pre-parsing to choose between oclif or Capitano.
|
|
||||||
* @param argv process.argv
|
|
||||||
*/
|
|
||||||
function routeCliFramework(argv: string[], options: AppOptions): void {
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(
|
|
||||||
`[debug] original argv0="${process.argv0}" argv=[${argv}] length=${
|
|
||||||
argv.length
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const cmdSlice = argv.slice(2);
|
|
||||||
|
|
||||||
// Look for commands that have been deleted, to print a notice
|
|
||||||
checkDeletedCommand(cmdSlice);
|
|
||||||
|
|
||||||
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.length > 1 && cmdSlice[0] === 'help') {
|
|
||||||
cmdSlice.shift();
|
|
||||||
cmdSlice.push('--help');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [isOclif, isTopic] = isOclifCommand(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)];
|
|
||||||
}
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(
|
|
||||||
`[debug] new argv=[${[
|
|
||||||
argv[0],
|
|
||||||
argv[1],
|
|
||||||
...oclifArgs,
|
|
||||||
]}] length=${oclifArgs.length + 2}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return require('./app-oclif').run(oclifArgs, options);
|
|
||||||
} else {
|
|
||||||
return require('./app-capitano').run(argv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param argvSlice process.argv.slice(2)
|
|
||||||
*/
|
|
||||||
function checkDeletedCommand(argvSlice: string[]): void {
|
|
||||||
if (argvSlice[0] === 'help') {
|
|
||||||
argvSlice = argvSlice.slice(1);
|
|
||||||
}
|
|
||||||
function replaced(
|
|
||||||
oldCmd: string,
|
|
||||||
alternative: string,
|
|
||||||
version: string,
|
|
||||||
verb = 'replaced',
|
|
||||||
) {
|
|
||||||
exitWithExpectedError(stripIndent`
|
|
||||||
Note: the command "balena ${oldCmd}" was ${verb} in CLI version ${version}.
|
|
||||||
Please use "balena ${alternative}" instead.
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
function removed(oldCmd: string, alternative: string, version: string) {
|
|
||||||
let msg = `Note: the command "balena ${oldCmd}" was removed in CLI version ${version}.`;
|
|
||||||
if (alternative) {
|
|
||||||
msg = [msg, alternative].join('\n');
|
|
||||||
}
|
|
||||||
exitWithExpectedError(msg);
|
|
||||||
}
|
|
||||||
const stopAlternative =
|
|
||||||
'Please use "balena ssh -s" to access the host OS, then use `balena-engine stop`.';
|
|
||||||
const cmds: { [cmd: string]: [(...args: any) => void, ...string[]] } = {
|
|
||||||
sync: [replaced, 'push', 'v11.0.0', 'removed'],
|
|
||||||
'local logs': [replaced, 'logs', 'v11.0.0'],
|
|
||||||
'local push': [replaced, 'push', 'v11.0.0'],
|
|
||||||
'local scan': [replaced, 'scan', 'v11.0.0'],
|
|
||||||
'local ssh': [replaced, 'ssh', 'v11.0.0'],
|
|
||||||
'local stop': [removed, stopAlternative, 'v11.0.0'],
|
|
||||||
};
|
|
||||||
let cmd: string | undefined;
|
|
||||||
if (argvSlice.length > 1) {
|
|
||||||
cmd = [argvSlice[0], argvSlice[1]].join(' ');
|
|
||||||
} else if (argvSlice.length > 0) {
|
|
||||||
cmd = argvSlice[0];
|
|
||||||
}
|
|
||||||
if (cmd && Object.getOwnPropertyNames(cmds).includes(cmd)) {
|
|
||||||
cmds[cmd][0](cmd, ...cmds[cmd].slice(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, boolean] {
|
|
||||||
// Look for commands that have been transitioned to oclif
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
// balena env rm
|
|
||||||
if (argvSlice[0] === 'env' && argvSlice[1] === 'rm') {
|
|
||||||
return [true, true];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [false, false];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
|
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
|
||||||
* call this function.
|
* call this function.
|
||||||
*/
|
*/
|
||||||
export function run(cliArgs = process.argv, options: AppOptions = {}): void {
|
export async function run(cliArgs = process.argv, options: AppOptions = {}) {
|
||||||
// globalInit() must be called very early on (before other imports) because
|
// globalInit() must be called very early on (before other imports) because
|
||||||
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
||||||
// shared options, and performs node version requirement checks.
|
// shared options, and performs node version requirement checks.
|
||||||
require('./app-common').globalInit();
|
globalInit();
|
||||||
return routeCliFramework(cliArgs, options);
|
await routeCliFramework(cliArgs, options);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Promise from 'bluebird';
|
import * as Bluebird from 'bluebird';
|
||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
@ -22,7 +22,7 @@ import * as Raven from 'raven';
|
|||||||
|
|
||||||
import * as patterns from './utils/patterns';
|
import * as patterns from './utils/patterns';
|
||||||
|
|
||||||
const captureException = Promise.promisify<string, Error>(
|
const captureException = Bluebird.promisify<string, Error>(
|
||||||
Raven.captureException,
|
Raven.captureException,
|
||||||
{ context: Raven },
|
{ context: Raven },
|
||||||
);
|
);
|
||||||
@ -104,7 +104,7 @@ const messages: {
|
|||||||
$ balena login`,
|
$ balena login`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function handleError(error: any) {
|
export async function handleError(error: any) {
|
||||||
let message = interpret(error);
|
let message = interpret(error);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
return;
|
return;
|
||||||
@ -116,7 +116,7 @@ export function handleError(error: any) {
|
|||||||
|
|
||||||
patterns.printErrorMessage(message!);
|
patterns.printErrorMessage(message!);
|
||||||
|
|
||||||
return captureException(error)
|
await captureException(error)
|
||||||
.timeout(1000)
|
.timeout(1000)
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
// Ignore any errors (from error logging, or timeouts)
|
// Ignore any errors (from error logging, or timeouts)
|
||||||
|
153
lib/preparser.ts
Normal file
153
lib/preparser.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* @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 { stripIndent } from 'common-tags';
|
||||||
|
|
||||||
|
import { exitWithExpectedError } from './utils/patterns';
|
||||||
|
|
||||||
|
export interface AppOptions {
|
||||||
|
// Prevent the default behaviour 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) {
|
||||||
|
if (process.env.DEBUG) {
|
||||||
|
console.log(
|
||||||
|
`[debug] original argv0="${process.argv0}" argv=[${argv}] length=${
|
||||||
|
argv.length
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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])) {
|
||||||
|
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.length > 1 && cmdSlice[0] === 'help') {
|
||||||
|
cmdSlice.shift();
|
||||||
|
cmdSlice.push('--help');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [isOclif, isTopic] = isOclifCommand(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)];
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
if (argvSlice[0] === 'help') {
|
||||||
|
argvSlice = argvSlice.slice(1);
|
||||||
|
}
|
||||||
|
function replaced(
|
||||||
|
oldCmd: string,
|
||||||
|
alternative: string,
|
||||||
|
version: string,
|
||||||
|
verb = 'replaced',
|
||||||
|
) {
|
||||||
|
exitWithExpectedError(stripIndent`
|
||||||
|
Note: the command "balena ${oldCmd}" was ${verb} in CLI version ${version}.
|
||||||
|
Please use "balena ${alternative}" instead.
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
function removed(oldCmd: string, alternative: string, version: string) {
|
||||||
|
let msg = `Note: the command "balena ${oldCmd}" was removed in CLI version ${version}.`;
|
||||||
|
if (alternative) {
|
||||||
|
msg = [msg, alternative].join('\n');
|
||||||
|
}
|
||||||
|
exitWithExpectedError(msg);
|
||||||
|
}
|
||||||
|
const stopAlternative =
|
||||||
|
'Please use "balena ssh -s" to access the host OS, then use `balena-engine stop`.';
|
||||||
|
const cmds: { [cmd: string]: [(...args: any) => void, ...string[]] } = {
|
||||||
|
sync: [replaced, 'push', 'v11.0.0', 'removed'],
|
||||||
|
'local logs': [replaced, 'logs', 'v11.0.0'],
|
||||||
|
'local push': [replaced, 'push', 'v11.0.0'],
|
||||||
|
'local scan': [replaced, 'scan', 'v11.0.0'],
|
||||||
|
'local ssh': [replaced, 'ssh', 'v11.0.0'],
|
||||||
|
'local stop': [removed, stopAlternative, 'v11.0.0'],
|
||||||
|
};
|
||||||
|
let cmd: string | undefined;
|
||||||
|
if (argvSlice.length > 1) {
|
||||||
|
cmd = [argvSlice[0], argvSlice[1]].join(' ');
|
||||||
|
} else if (argvSlice.length > 0) {
|
||||||
|
cmd = argvSlice[0];
|
||||||
|
}
|
||||||
|
if (cmd && Object.getOwnPropertyNames(cmds).includes(cmd)) {
|
||||||
|
cmds[cmd][0](cmd, ...cmds[cmd].slice(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const convertedCommands = ['env:add', 'env:rm', 'version'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, 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];
|
||||||
|
}
|
43
lib/utils/common-flags.ts
Normal file
43
lib/utils/common-flags.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* @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 { flags } from '@oclif/command';
|
||||||
|
|
||||||
|
type IBooleanFlag<T> = import('@oclif/parser/lib/flags').IBooleanFlag<T>;
|
||||||
|
|
||||||
|
export const application = flags.string({
|
||||||
|
char: 'a',
|
||||||
|
description: 'application name',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const device = flags.string({
|
||||||
|
char: 'd',
|
||||||
|
description: 'device UUID',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const help: IBooleanFlag<void> = flags.help({ char: 'h' });
|
||||||
|
|
||||||
|
export const quiet: IBooleanFlag<boolean> = flags.boolean({
|
||||||
|
char: 'q',
|
||||||
|
description: 'suppress warning messages',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const verbose: IBooleanFlag<boolean> = flags.boolean({
|
||||||
|
char: 'v',
|
||||||
|
description: 'produce verbose output',
|
||||||
|
});
|
@ -17,11 +17,6 @@
|
|||||||
|
|
||||||
import * as Config from '@oclif/config';
|
import * as Config from '@oclif/config';
|
||||||
|
|
||||||
export const convertedCommands = {
|
|
||||||
'env:add': 'env add',
|
|
||||||
'env:rm': 'env rm',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is a partial copy-and-paste of
|
* This class is a partial copy-and-paste of
|
||||||
* @oclif/plugin-help/command/CommandHelp, which is used to generate oclif's
|
* @oclif/plugin-help/command/CommandHelp, which is used to generate oclif's
|
||||||
|
@ -216,10 +216,14 @@ async function getOrSelectApplication(
|
|||||||
throw new Error(`"${deviceType}" is not a valid device type`);
|
throw new Error(`"${deviceType}" is not a valid device type`);
|
||||||
}
|
}
|
||||||
const compatibleDeviceTypes = _(allDeviceTypes)
|
const compatibleDeviceTypes = _(allDeviceTypes)
|
||||||
.filter(dt =>
|
.filter(
|
||||||
sdk.models.os.isArchitectureCompatibleWith(deviceTypeManifest.arch, dt.arch) &&
|
dt =>
|
||||||
|
sdk.models.os.isArchitectureCompatibleWith(
|
||||||
|
deviceTypeManifest.arch,
|
||||||
|
dt.arch,
|
||||||
|
) &&
|
||||||
!!dt.isDependent === !!deviceTypeManifest.isDependent &&
|
!!dt.isDependent === !!deviceTypeManifest.isDependent &&
|
||||||
dt.state !== 'DISCONTINUED'
|
dt.state !== 'DISCONTINUED',
|
||||||
)
|
)
|
||||||
.map(type => type.slug)
|
.map(type => type.slug)
|
||||||
.value();
|
.value();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "balena-cli",
|
"name": "balena-cli",
|
||||||
"version": "11.11.0",
|
"version": "11.11.0",
|
||||||
"description": "The official balena CLI tool",
|
"description": "The official balena CLI tool",
|
||||||
"main": "./build/actions/index.js",
|
"main": "./build/app.js",
|
||||||
"homepage": "https://github.com/balena-io/balena-cli",
|
"homepage": "https://github.com/balena-io/balena-cli",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -29,6 +29,7 @@
|
|||||||
"node_modules/raven/lib/instrumentation/*.js"
|
"node_modules/raven/lib/instrumentation/*.js"
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
|
"build/**/*.js",
|
||||||
"build/actions-oclif",
|
"build/actions-oclif",
|
||||||
"build/auth/pages/*.ejs",
|
"build/auth/pages/*.ejs",
|
||||||
"build/hooks",
|
"build/hooks",
|
||||||
|
Reference in New Issue
Block a user