mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-04-24 13:05:55 +00:00
Merge pull request #1414 from balena-io/1391-refactor-oclif
Migrate "env rename" and "envs" to oclif and refactor the preparser
This commit is contained in:
commit
a6f329750c
automation/capitanodoc
doc
lib
actions-oclif
actions
app-capitano.coffeeapp-capitano.d.tsapp-oclif.tsapp.tserrors.tspreparser.tsutils
tests
typings/intercept-stdout
@ -49,8 +49,9 @@ const capitanoDoc = {
|
||||
{
|
||||
title: 'Environment Variables',
|
||||
files: [
|
||||
'build/actions/environment-variables.js',
|
||||
'build/actions-oclif/envs.js',
|
||||
'build/actions-oclif/env/add.js',
|
||||
'build/actions-oclif/env/rename.js',
|
||||
'build/actions-oclif/env/rm.js',
|
||||
],
|
||||
},
|
||||
|
@ -572,16 +572,16 @@ confirm non interactively
|
||||
|
||||
## envs
|
||||
|
||||
Use this command to list the environment variables of an application
|
||||
or device.
|
||||
List the environment or config variables of an application or device,
|
||||
as selected by the respective command-line options.
|
||||
|
||||
The --config option is used to list "config" variables that configure
|
||||
balena features.
|
||||
The --config option is used to list "configuration variables" that
|
||||
control balena features.
|
||||
|
||||
Service-specific variables are not currently supported. The following
|
||||
examples list variables that apply to all services in an app or device.
|
||||
|
||||
Example:
|
||||
Examples:
|
||||
|
||||
$ balena envs --application MyApp
|
||||
$ balena envs --application MyApp --config
|
||||
@ -589,18 +589,22 @@ Example:
|
||||
|
||||
### Options
|
||||
|
||||
#### --application, -a, --app <application>
|
||||
#### -a, --application APPLICATION
|
||||
|
||||
application name
|
||||
|
||||
#### --device, -d <device>
|
||||
|
||||
device uuid
|
||||
|
||||
#### --config, -c, -v, --verbose
|
||||
#### -c, --config
|
||||
|
||||
show config variables
|
||||
|
||||
#### -d, --device DEVICE
|
||||
|
||||
device UUID
|
||||
|
||||
#### -v, --verbose
|
||||
|
||||
produce verbose output
|
||||
|
||||
## env rm ID
|
||||
|
||||
Remove an environment variable from an application or device, as selected
|
||||
@ -624,7 +628,7 @@ Examples:
|
||||
|
||||
#### ID
|
||||
|
||||
environment variable id
|
||||
environment variable numeric database ID
|
||||
|
||||
### Options
|
||||
|
||||
@ -678,12 +682,12 @@ device UUID
|
||||
|
||||
suppress warning messages
|
||||
|
||||
## env rename <id> <value>
|
||||
## env rename ID VALUE
|
||||
|
||||
Use this command to change the value of an application or device
|
||||
environment variable.
|
||||
|
||||
The --device option selects a device instead of an application.
|
||||
Change the value of an environment variable for an application or device,
|
||||
as selected by the '--device' option. The variable is identified by its
|
||||
database ID, rather than its name. The 'balena envs' command can be used
|
||||
to list the variable's ID.
|
||||
|
||||
Service-specific variables are not currently supported. The following
|
||||
examples modify variables that apply to all services in an app or device.
|
||||
@ -693,11 +697,21 @@ Examples:
|
||||
$ balena env rename 376 emacs
|
||||
$ balena env rename 376 emacs --device
|
||||
|
||||
### Arguments
|
||||
|
||||
#### ID
|
||||
|
||||
environment variable numeric database ID
|
||||
|
||||
#### VALUE
|
||||
|
||||
variable value; if omitted, use value from CLI's environment
|
||||
|
||||
### Options
|
||||
|
||||
#### --device, -d
|
||||
#### -d, --device
|
||||
|
||||
device
|
||||
select a device variable instead of an application variable
|
||||
|
||||
# Tags
|
||||
|
||||
|
88
lib/actions-oclif/env/add.ts
vendored
88
lib/actions-oclif/env/add.ts
vendored
@ -17,7 +17,9 @@
|
||||
|
||||
import { Command, flags } from '@oclif/command';
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
|
||||
interface FlagsDef {
|
||||
@ -71,69 +73,55 @@ export default class EnvAddCmd extends Command {
|
||||
'env add ' + new CommandHelp({ args: EnvAddCmd.args }).defaultUsage();
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
application: flags.string({
|
||||
char: 'a',
|
||||
description: 'application name',
|
||||
exclusive: ['device'],
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
application: _.assign({ exclusive: ['device'] }, cf.application),
|
||||
device: _.assign({ exclusive: ['application'] }, cf.device),
|
||||
help: cf.help,
|
||||
quiet: cf.quiet,
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
EnvAddCmd,
|
||||
);
|
||||
const Bluebird = await import('bluebird');
|
||||
const _ = await import('lodash');
|
||||
const cmd = this;
|
||||
const balena = (await import('balena-sdk')).fromSharedOptions();
|
||||
const { exitWithExpectedError } = await import('../../utils/patterns');
|
||||
|
||||
const cmd = this;
|
||||
if (params.value == null) {
|
||||
params.value = process.env[params.name];
|
||||
|
||||
await Bluebird.try(async function() {
|
||||
if (params.value == null) {
|
||||
params.value = process.env[params.name];
|
||||
|
||||
if (params.value == null) {
|
||||
throw new Error(
|
||||
`Environment value not found for variable: ${params.name}`,
|
||||
);
|
||||
} else if (!options.quiet) {
|
||||
cmd.warn(
|
||||
`Using ${params.name}=${params.value} from CLI process environment`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const reservedPrefixes = await getReservedPrefixes();
|
||||
const isConfigVar = _.some(reservedPrefixes, prefix =>
|
||||
_.startsWith(params.name, prefix),
|
||||
);
|
||||
|
||||
if (options.application) {
|
||||
return balena.models.application[
|
||||
isConfigVar ? 'configVar' : 'envVar'
|
||||
].set(options.application, params.name, params.value);
|
||||
} else if (options.device) {
|
||||
return balena.models.device[isConfigVar ? 'configVar' : 'envVar'].set(
|
||||
options.device,
|
||||
params.name,
|
||||
params.value,
|
||||
throw new Error(
|
||||
`Environment value not found for variable: ${params.name}`,
|
||||
);
|
||||
} else if (!options.quiet) {
|
||||
cmd.warn(
|
||||
`Using ${params.name}=${params.value} from CLI process environment`,
|
||||
);
|
||||
} else {
|
||||
exitWithExpectedError('You must specify an application or device');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const reservedPrefixes = await getReservedPrefixes();
|
||||
const isConfigVar = _.some(reservedPrefixes, prefix =>
|
||||
_.startsWith(params.name, prefix),
|
||||
);
|
||||
const varType = isConfigVar ? 'configVar' : 'envVar';
|
||||
|
||||
if (options.application) {
|
||||
await balena.models.application[varType].set(
|
||||
options.application,
|
||||
params.name,
|
||||
params.value,
|
||||
);
|
||||
} else if (options.device) {
|
||||
await balena.models.device[varType].set(
|
||||
options.device,
|
||||
params.name,
|
||||
params.value,
|
||||
);
|
||||
} else {
|
||||
exitWithExpectedError('You must specify an application or device');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
96
lib/actions-oclif/env/rename.ts
vendored
Normal file
96
lib/actions-oclif/env/rename.ts
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-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';
|
||||
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
|
||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||
|
||||
interface FlagsDef {
|
||||
device: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
id: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export default class EnvRenameCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Change the value of an environment variable for an app or device.
|
||||
|
||||
Change the value of an environment variable for an application or device,
|
||||
as selected by the '--device' option. The variable is identified by its
|
||||
database ID, rather than its name. The 'balena envs' command can be used
|
||||
to list the variable's ID.
|
||||
|
||||
Service-specific variables are not currently supported. The following
|
||||
examples modify variables that apply to all services in an app or device.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena env rename 376 emacs',
|
||||
'$ balena env rename 376 emacs --device',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'id',
|
||||
required: true,
|
||||
description: 'environment variable numeric database ID',
|
||||
parse: input => parseInt(input, 10),
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
required: true,
|
||||
description:
|
||||
"variable value; if omitted, use value from CLI's environment",
|
||||
},
|
||||
];
|
||||
|
||||
// hardcoded 'env add' to avoid oclif's 'env:add' topic syntax
|
||||
public static usage =
|
||||
'env rename ' + new CommandHelp({ args: EnvRenameCmd.args }).defaultUsage();
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
device: flags.boolean({
|
||||
char: 'd',
|
||||
description:
|
||||
'select a device variable instead of an application variable',
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
EnvRenameCmd,
|
||||
);
|
||||
const balena = (await import('balena-sdk')).fromSharedOptions();
|
||||
|
||||
await balena.pine.patch({
|
||||
resource: options.device
|
||||
? 'device_environment_variable'
|
||||
: 'application_environment_variable',
|
||||
id: params.id,
|
||||
body: {
|
||||
value: params.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
34
lib/actions-oclif/env/rm.ts
vendored
34
lib/actions-oclif/env/rm.ts
vendored
@ -54,7 +54,7 @@ export default class EnvRmCmd extends Command {
|
||||
{
|
||||
name: 'id',
|
||||
required: true,
|
||||
description: 'environment variable id',
|
||||
description: 'environment variable numeric database ID',
|
||||
},
|
||||
];
|
||||
|
||||
@ -89,23 +89,23 @@ export default class EnvRmCmd extends Command {
|
||||
);
|
||||
}
|
||||
|
||||
return patterns
|
||||
.confirm(
|
||||
try {
|
||||
await patterns.confirm(
|
||||
options.yes || false,
|
||||
'Are you sure you want to delete the environment variable?',
|
||||
)
|
||||
.then(function() {
|
||||
if (options.device) {
|
||||
return balena.pine.delete({
|
||||
resource: 'device_environment_variable',
|
||||
id: params.id,
|
||||
});
|
||||
} else {
|
||||
return balena.pine.delete({
|
||||
resource: 'application_environment_variable',
|
||||
id: params.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.message === 'Aborted') {
|
||||
return patterns.exitWithExpectedError(err);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
await balena.pine.delete({
|
||||
resource: options.device
|
||||
? 'device_environment_variable'
|
||||
: 'application_environment_variable',
|
||||
id: params.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
95
lib/actions-oclif/envs.ts
Normal file
95
lib/actions-oclif/envs.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-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 { ApplicationVariable, DeviceVariable } from 'balena-sdk';
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { CommandHelp } from '../utils/oclif-utils';
|
||||
|
||||
interface FlagsDef {
|
||||
application?: string;
|
||||
config: boolean;
|
||||
device?: string;
|
||||
help: void;
|
||||
verbose: boolean;
|
||||
}
|
||||
|
||||
export default class EnvsCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
List the environment or config variables of an app or device.
|
||||
|
||||
List the environment or config variables of an application or device,
|
||||
as selected by the respective command-line options.
|
||||
|
||||
The --config option is used to list "configuration variables" that
|
||||
control balena features.
|
||||
|
||||
Service-specific variables are not currently supported. The following
|
||||
examples list variables that apply to all services in an app or device.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena envs --application MyApp',
|
||||
'$ balena envs --application MyApp --config',
|
||||
'$ balena envs --device 7cf02a6',
|
||||
];
|
||||
|
||||
public static usage = (
|
||||
'envs ' + new CommandHelp({ args: EnvsCmd.args }).defaultUsage()
|
||||
).trim();
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
application: _.assign({ exclusive: ['device'] }, cf.application),
|
||||
config: flags.boolean({
|
||||
char: 'c',
|
||||
description: 'show config variables',
|
||||
}),
|
||||
device: _.assign({ exclusive: ['application'] }, cf.device),
|
||||
help: cf.help,
|
||||
verbose: cf.verbose,
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
|
||||
const balena = (await import('balena-sdk')).fromSharedOptions();
|
||||
const visuals = await import('resin-cli-visuals');
|
||||
const { exitWithExpectedError } = await import('../utils/patterns');
|
||||
const cmd = this;
|
||||
|
||||
let environmentVariables: ApplicationVariable[] | DeviceVariable[];
|
||||
if (options.application) {
|
||||
environmentVariables = await balena.models.application[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByApplication(options.application);
|
||||
} else if (options.device) {
|
||||
environmentVariables = await balena.models.device[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByDevice(options.device);
|
||||
} else {
|
||||
return exitWithExpectedError('You must specify an application or device');
|
||||
}
|
||||
|
||||
if (_.isEmpty(environmentVariables)) {
|
||||
return exitWithExpectedError('No environment variables found');
|
||||
}
|
||||
|
||||
cmd.log(
|
||||
visuals.table.horizontal(environmentVariables, ['id', 'name', 'value']),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,154 +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 { ApplicationVariable, DeviceVariable } from 'balena-sdk';
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { CommandDefinition } from 'capitano';
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
import { normalizeUuidProp } from '../utils/normalization';
|
||||
import * as commandOptions from './command-options';
|
||||
|
||||
export const list: CommandDefinition<
|
||||
{},
|
||||
{
|
||||
application?: string;
|
||||
device?: string;
|
||||
config: boolean;
|
||||
}
|
||||
> = {
|
||||
signature: 'envs',
|
||||
description: 'list all environment variables',
|
||||
help: stripIndent`
|
||||
Use this command to list the environment variables of an application
|
||||
or device.
|
||||
|
||||
The --config option is used to list "config" variables that configure
|
||||
balena features.
|
||||
|
||||
Service-specific variables are not currently supported. The following
|
||||
examples list variables that apply to all services in an app or device.
|
||||
|
||||
Example:
|
||||
|
||||
$ balena envs --application MyApp
|
||||
$ balena envs --application MyApp --config
|
||||
$ balena envs --device 7cf02a6
|
||||
`,
|
||||
options: [
|
||||
commandOptions.optionalApplication,
|
||||
commandOptions.optionalDevice,
|
||||
|
||||
{
|
||||
signature: 'config',
|
||||
description: 'show config variables',
|
||||
boolean: true,
|
||||
alias: ['c', 'v', 'verbose'],
|
||||
},
|
||||
],
|
||||
permission: 'user',
|
||||
async action(_params, options, done) {
|
||||
normalizeUuidProp(options, 'device');
|
||||
const _ = await import('lodash');
|
||||
const balena = (await import('balena-sdk')).fromSharedOptions();
|
||||
const visuals = await import('resin-cli-visuals');
|
||||
|
||||
const { exitWithExpectedError } = await import('../utils/patterns');
|
||||
|
||||
return Bluebird.try(function(): Bluebird<
|
||||
DeviceVariable[] | ApplicationVariable[]
|
||||
> {
|
||||
if (options.application) {
|
||||
return balena.models.application[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByApplication(options.application);
|
||||
} else if (options.device) {
|
||||
return balena.models.device[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByDevice(options.device);
|
||||
} else {
|
||||
return exitWithExpectedError(
|
||||
'You must specify an application or device',
|
||||
);
|
||||
}
|
||||
})
|
||||
.tap(function(environmentVariables) {
|
||||
if (_.isEmpty(environmentVariables)) {
|
||||
exitWithExpectedError('No environment variables found');
|
||||
}
|
||||
|
||||
console.log(
|
||||
visuals.table.horizontal(environmentVariables, [
|
||||
'id',
|
||||
'name',
|
||||
'value',
|
||||
]),
|
||||
);
|
||||
})
|
||||
.nodeify(done);
|
||||
},
|
||||
};
|
||||
|
||||
export const rename: CommandDefinition<
|
||||
{
|
||||
id: number;
|
||||
value: string;
|
||||
},
|
||||
{
|
||||
device: boolean;
|
||||
}
|
||||
> = {
|
||||
signature: 'env rename <id> <value>',
|
||||
description: 'rename an environment variable',
|
||||
help: stripIndent`
|
||||
Use this command to change the value of an application or device
|
||||
environment variable.
|
||||
|
||||
The --device option selects a device instead of an application.
|
||||
|
||||
Service-specific variables are not currently supported. The following
|
||||
examples modify variables that apply to all services in an app or device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena env rename 376 emacs
|
||||
$ balena env rename 376 emacs --device
|
||||
`,
|
||||
permission: 'user',
|
||||
options: [commandOptions.booleanDevice],
|
||||
async action(params, options, done) {
|
||||
const balena = (await import('balena-sdk')).fromSharedOptions();
|
||||
|
||||
return Bluebird.try(function() {
|
||||
if (options.device) {
|
||||
return balena.pine.patch({
|
||||
resource: 'device_environment_variable',
|
||||
id: params.id,
|
||||
body: {
|
||||
value: params.value,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return balena.pine.patch({
|
||||
resource: 'application_environment_variable',
|
||||
id: params.id,
|
||||
body: {
|
||||
value: params.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}).nodeify(done);
|
||||
},
|
||||
};
|
@ -90,15 +90,20 @@ general = (params, options, done) ->
|
||||
|
||||
if options.verbose
|
||||
console.log('\nAdditional commands:\n')
|
||||
print parse(groupedCommands.secondary).concat(getOclifHelpLinePairs()).sort()
|
||||
secondaryCommandPromise = getOclifHelpLinePairs()
|
||||
.then (oclifHelpLinePairs) ->
|
||||
print parse(groupedCommands.secondary).concat(oclifHelpLinePairs).sort()
|
||||
else
|
||||
console.log('\nRun `balena help --verbose` to list additional commands')
|
||||
secondaryCommandPromise = Promise.resolve()
|
||||
|
||||
if not _.isEmpty(capitano.state.globalOptions)
|
||||
console.log('\nGlobal Options:\n')
|
||||
print parse(capitano.state.globalOptions).sort()
|
||||
|
||||
return done()
|
||||
secondaryCommandPromise
|
||||
.then ->
|
||||
if not _.isEmpty(capitano.state.globalOptions)
|
||||
console.log('\nGlobal Options:\n')
|
||||
print parse(capitano.state.globalOptions).sort()
|
||||
done()
|
||||
.catch(done)
|
||||
|
||||
command = (params, options, done) ->
|
||||
capitano.state.getMatchCommand params.command, (error, command) ->
|
||||
|
@ -16,20 +16,29 @@
|
||||
*/
|
||||
|
||||
import { Command } from '@oclif/command';
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
|
||||
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 EnvRmCmd = require('../actions-oclif/env/rm').default;
|
||||
const VersionCmd = require('../actions-oclif/version').default;
|
||||
return [EnvAddCmd, EnvRmCmd, VersionCmd].map(getCmdUsageDescriptionLinePair);
|
||||
export async function getOclifHelpLinePairs(): Promise<
|
||||
Array<[string, string]>
|
||||
> {
|
||||
const { convertedCommands } = await import('../preparser');
|
||||
const cmdClasses: Array<Promise<typeof Command>> = [];
|
||||
for (const convertedCmd of convertedCommands) {
|
||||
const [topic, cmd] = convertedCmd.split(':');
|
||||
const pathComponents = ['..', 'actions-oclif', topic];
|
||||
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();
|
||||
let description = '';
|
||||
// note: [^] matches any characters (including line breaks), achieving the
|
||||
|
@ -19,7 +19,6 @@ module.exports =
|
||||
app: require('./app')
|
||||
auth: require('./auth')
|
||||
device: require('./device')
|
||||
env: require('./environment-variables')
|
||||
tags: require('./tags')
|
||||
keys: require('./keys')
|
||||
logs: require('./logs')
|
||||
|
@ -83,10 +83,6 @@ capitano.command(actions.keys.add)
|
||||
capitano.command(actions.keys.info)
|
||||
capitano.command(actions.keys.remove)
|
||||
|
||||
# ---------- Env Module ----------
|
||||
capitano.command(actions.env.list)
|
||||
capitano.command(actions.env.rename)
|
||||
|
||||
# ---------- Tags Module ----------
|
||||
capitano.command(actions.tags.list)
|
||||
capitano.command(actions.tags.set)
|
||||
|
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 { ExitError } from '@oclif/errors';
|
||||
|
||||
import { AppOptions } from './app';
|
||||
import { trackPromise } from './hooks/prerun/track';
|
||||
|
||||
class CustomMain extends Main {
|
||||
@ -32,10 +31,12 @@ class CustomMain extends Main {
|
||||
}
|
||||
}
|
||||
|
||||
type AppOptions = import('./preparser').AppOptions;
|
||||
|
||||
/**
|
||||
* oclif CLI entrypoint
|
||||
*/
|
||||
export function run(command: string[], options: AppOptions) {
|
||||
export async function run(command: string[], options: AppOptions) {
|
||||
const runPromise = CustomMain.run(command).then(
|
||||
() => {
|
||||
if (!options.noFlush) {
|
||||
@ -51,7 +52,9 @@ export function run(command: string[], options: AppOptions) {
|
||||
}
|
||||
},
|
||||
);
|
||||
return Promise.all([trackPromise, runPromise]).catch(
|
||||
require('./errors').handleError,
|
||||
);
|
||||
try {
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
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];
|
||||
}
|
||||
import { globalInit } from './app-common';
|
||||
import { AppOptions, routeCliFramework } from './preparser';
|
||||
|
||||
/**
|
||||
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
|
||||
* 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
|
||||
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
||||
// shared options, and performs node version requirement checks.
|
||||
require('./app-common').globalInit();
|
||||
return routeCliFramework(cliArgs, options);
|
||||
globalInit();
|
||||
await routeCliFramework(cliArgs, options);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as Promise from 'bluebird';
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
import * as os from 'os';
|
||||
@ -22,7 +22,7 @@ import * as Raven from 'raven';
|
||||
|
||||
import * as patterns from './utils/patterns';
|
||||
|
||||
const captureException = Promise.promisify<string, Error>(
|
||||
const captureException = Bluebird.promisify<string, Error>(
|
||||
Raven.captureException,
|
||||
{ context: Raven },
|
||||
);
|
||||
@ -104,7 +104,7 @@ const messages: {
|
||||
$ balena login`,
|
||||
};
|
||||
|
||||
export function handleError(error: any) {
|
||||
export async function handleError(error: any) {
|
||||
let message = interpret(error);
|
||||
if (message == null) {
|
||||
return;
|
||||
@ -116,7 +116,7 @@ export function handleError(error: any) {
|
||||
|
||||
patterns.printErrorMessage(message!);
|
||||
|
||||
return captureException(error)
|
||||
await captureException(error)
|
||||
.timeout(1000)
|
||||
.catch(function() {
|
||||
// Ignore any errors (from error logging, or timeouts)
|
||||
|
159
lib/preparser.ts
Normal file
159
lib/preparser.ts
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @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 = [
|
||||
'envs',
|
||||
'env:add',
|
||||
'env:rename',
|
||||
'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';
|
||||
|
||||
export const convertedCommands = {
|
||||
'env:add': 'env add',
|
||||
'env:rm': 'env rm',
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is a partial copy-and-paste of
|
||||
* @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`);
|
||||
}
|
||||
const compatibleDeviceTypes = _(allDeviceTypes)
|
||||
.filter(dt =>
|
||||
sdk.models.os.isArchitectureCompatibleWith(deviceTypeManifest.arch, dt.arch) &&
|
||||
!!dt.isDependent === !!deviceTypeManifest.isDependent &&
|
||||
dt.state !== 'DISCONTINUED'
|
||||
.filter(
|
||||
dt =>
|
||||
sdk.models.os.isArchitectureCompatibleWith(
|
||||
deviceTypeManifest.arch,
|
||||
dt.arch,
|
||||
) &&
|
||||
!!dt.isDependent === !!deviceTypeManifest.isDependent &&
|
||||
dt.state !== 'DISCONTINUED',
|
||||
)
|
||||
.map(type => type.slug)
|
||||
.value();
|
||||
|
26
npm-shrinkwrap.json
generated
26
npm-shrinkwrap.json
generated
@ -7394,6 +7394,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"intercept-stdout": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/intercept-stdout/-/intercept-stdout-0.1.2.tgz",
|
||||
"integrity": "sha1-Emq/H65sUJpCipjGGmMVWQQq6f0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.toarray": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"interpret": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
|
||||
@ -8056,6 +8065,12 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||
},
|
||||
"lodash._arraycopy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
|
||||
"integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._basecopy": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
|
||||
@ -8170,6 +8185,17 @@
|
||||
"lodash._reinterpolate": "~3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.toarray": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-3.0.2.tgz",
|
||||
"integrity": "sha1-KyBPD6T1HChcbwDIHRzqWiMEEXk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._arraycopy": "^3.0.0",
|
||||
"lodash._basevalues": "^3.0.0",
|
||||
"lodash.keys": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "balena-cli",
|
||||
"version": "11.11.0",
|
||||
"description": "The official balena CLI tool",
|
||||
"main": "./build/actions/index.js",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -29,6 +29,7 @@
|
||||
"node_modules/raven/lib/instrumentation/*.js"
|
||||
],
|
||||
"assets": [
|
||||
"build/**/*.js",
|
||||
"build/actions-oclif",
|
||||
"build/auth/pages/*.ejs",
|
||||
"build/hooks",
|
||||
@ -124,6 +125,7 @@
|
||||
"gulp-coffee": "^2.2.0",
|
||||
"gulp-inline-source": "^2.1.0",
|
||||
"gulp-shell": "^0.5.2",
|
||||
"intercept-stdout": "^0.1.2",
|
||||
"mocha": "^6.2.0",
|
||||
"nock": "^10.0.6",
|
||||
"parse-link-header": "~1.0.1",
|
||||
|
34
tests/commands/env/rename.spec.ts
vendored
Normal file
34
tests/commands/env/rename.spec.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-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 chai from 'chai';
|
||||
import { balenaAPIMock, runCommand } from '../../helpers';
|
||||
|
||||
describe('balena env rename', function() {
|
||||
it('should successfully rename an environment variable', async () => {
|
||||
const mock = balenaAPIMock();
|
||||
mock.patch(/device_environment_variable\(376\)/).reply(200, 'OK');
|
||||
|
||||
const { out, err } = await runCommand('env rename 376 emacs --device');
|
||||
|
||||
chai.expect(out.join('')).to.equal('');
|
||||
chai.expect(err.join('')).to.equal('');
|
||||
|
||||
// @ts-ignore
|
||||
mock.remove();
|
||||
});
|
||||
});
|
@ -59,9 +59,9 @@ Additional commands:
|
||||
device shutdown <uuid> shutdown a device
|
||||
devices supported list all supported devices
|
||||
env add name [value] add an environment or config variable to an application or device
|
||||
env rename <id> <value> rename an environment variable
|
||||
env rename id value change the value of an environment variable for an app or device
|
||||
env rm id remove an environment variable from an application or device
|
||||
envs list all environment variables
|
||||
envs list the environment or config variables of an app or device
|
||||
key <id> list a single ssh key
|
||||
key add <name> [path] add a SSH key to balena
|
||||
key rm <id> remove a ssh key
|
||||
|
@ -1,28 +1,42 @@
|
||||
/**
|
||||
* @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 intercept = require('intercept-stdout');
|
||||
import * as nock from 'nock';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as balenaCLI from '../build/app';
|
||||
|
||||
export const runCommand = async (cmd: string) => {
|
||||
const preArgs = [process.argv[0], path.join(process.cwd(), 'bin', 'balena')];
|
||||
|
||||
const oldStdOut = process.stdout.write;
|
||||
const oldStdErr = process.stderr.write;
|
||||
|
||||
const err: string[] = [];
|
||||
const out: string[] = [];
|
||||
|
||||
// @ts-ignore
|
||||
process.stdout.write = (log: string) => {
|
||||
const stdoutHook = (log: string | Buffer) => {
|
||||
// Skip over debug messages
|
||||
if (!log.startsWith('[debug]')) {
|
||||
if (typeof log === 'string' && !log.startsWith('[debug]')) {
|
||||
out.push(log);
|
||||
}
|
||||
oldStdOut(log);
|
||||
};
|
||||
// @ts-ignore
|
||||
process.stderr.write = (log: string) => {
|
||||
const stderrHook = (log: string | Buffer) => {
|
||||
// Skip over debug messages
|
||||
if (
|
||||
typeof log === 'string' &&
|
||||
!log.startsWith('[debug]') &&
|
||||
// TODO stop this warning message from appearing when running
|
||||
// sdk.setSharedOptions multiple times in the same process
|
||||
@ -30,26 +44,19 @@ export const runCommand = async (cmd: string) => {
|
||||
) {
|
||||
err.push(log);
|
||||
}
|
||||
oldStdErr(log);
|
||||
};
|
||||
const unhookIntercept = intercept(stdoutHook, stderrHook);
|
||||
|
||||
try {
|
||||
await balenaCLI.run(preArgs.concat(cmd.split(' ')), {
|
||||
noFlush: true,
|
||||
});
|
||||
|
||||
process.stdout.write = oldStdOut;
|
||||
process.stderr.write = oldStdErr;
|
||||
|
||||
return {
|
||||
err,
|
||||
out,
|
||||
};
|
||||
} catch (err) {
|
||||
process.stdout.write = oldStdOut;
|
||||
process.stderr.write = oldStdErr;
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
unhookIntercept();
|
||||
}
|
||||
};
|
||||
|
||||
|
28
typings/intercept-stdout/index.d.ts
vendored
Normal file
28
typings/intercept-stdout/index.d.ts
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
declare module 'intercept-stdout' {
|
||||
type hookFunction = (txt: string) => string | void;
|
||||
type unhookFunction = () => void;
|
||||
|
||||
function intercept(
|
||||
stdoutIntercept: hookFunction,
|
||||
stderrIntercept?: hookFunction,
|
||||
): unhookFunction;
|
||||
|
||||
export = intercept;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user