From 6cfe2bdc49b8af142a8ca740d70472d1ece1951c Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 12 Dec 2014 17:20:29 -0400 Subject: [PATCH] Make use of Capitano for command line parsing --- lib/actions/app.coffee | 30 +- lib/actions/auth.coffee | 11 +- lib/actions/device.coffee | 21 +- lib/actions/environment-variables.coffee | 19 +- lib/actions/help.coffee | 111 ++++ lib/actions/index.coffee | 1 + lib/actions/keys.coffee | 20 +- lib/actions/logs.coffee | 13 +- lib/actions/os.coffee | 20 +- lib/actions/preferences.coffee | 3 +- lib/app.coffee | 478 ++++++++++++++---- lib/cli/cli.coffee | 101 ---- .../permissions.coffee} | 3 +- .../permissions.spec.coffee} | 12 +- package.json | 4 +- 15 files changed, 568 insertions(+), 279 deletions(-) create mode 100644 lib/actions/help.coffee delete mode 100644 lib/cli/cli.coffee rename lib/{cli/cli-permissions.coffee => permissions/permissions.coffee} (83%) rename lib/{cli/cli-permissions.spec.coffee => permissions/permissions.spec.coffee} (84%) diff --git a/lib/actions/app.coffee b/lib/actions/app.coffee index bae11f3b..6201f683 100644 --- a/lib/actions/app.coffee +++ b/lib/actions/app.coffee @@ -2,14 +2,14 @@ _ = require('lodash') async = require('async') gitCli = require('git-cli') resin = require('../resin') -cli = require('../cli/cli') ui = require('../ui') +permissions = require('../permissions/permissions') -exports.create = (name) -> +exports.create = permissions.user (params, options) -> async.waterfall [ (callback) -> - deviceType = cli.getArgument('type') + deviceType = options.type if deviceType? return callback(null, deviceType) @@ -23,11 +23,11 @@ exports.create = (name) -> # Maybe we should break or handle better? slugifiedType = resin.device.getDeviceSlug(type) - resin.models.application.create(name, slugifiedType, callback) + resin.models.application.create(params.name, slugifiedType, callback) ], resin.errors.handle -exports.list = -> +exports.list = permissions.user -> resin.models.application.getAll (error, applications) -> resin.errors.handle(error) if error? @@ -40,8 +40,8 @@ exports.list = -> return application , [ 'ID', 'Name', 'Device Type', 'Online Devices', 'All Devices' ] -exports.info = (id) -> - resin.models.application.get id, (error, application) -> +exports.info = permissions.user (params) -> + resin.models.application.get params.id, (error, application) -> resin.errors.handle(error) if error? resin.log.out ui.widgets.table.vertical application, (application) -> @@ -50,18 +50,16 @@ exports.info = (id) -> return application , [ 'ID', 'Name', 'Device Type', 'Git Repository', 'Commit' ] -exports.restart = (id) -> - - resin.models.application.restart id, (error) -> +exports.restart = permissions.user (params) -> + resin.models.application.restart params.id, (error) -> resin.errors.handle(error) if error? -exports.remove = (id) -> - confirmArgument = cli.getArgument('yes') - ui.patterns.remove 'application', confirmArgument, (callback) -> - resin.models.application.remove(id, callback) +exports.remove = permissions.user (params, options) -> + ui.patterns.remove 'application', options.yes, (callback) -> + resin.models.application.remove(params.id, callback) , resin.errors.handle -exports.init = (id) -> +exports.init = permissions.user (params) -> currentDirectory = process.cwd() @@ -77,7 +75,7 @@ exports.init = (id) -> return callback() (callback) -> - resin.models.application.get(id, callback) + resin.models.application.get(params.id, callback) (application, callback) -> resin.vcs.initProjectWithApplication(application, currentDirectory, callback) diff --git a/lib/actions/auth.coffee b/lib/actions/auth.coffee index d3facfff..1e1d26d2 100644 --- a/lib/actions/auth.coffee +++ b/lib/actions/auth.coffee @@ -3,13 +3,14 @@ open = require('open') async = require('async') resin = require('../resin') ui = require('../ui') +permissions = require('../permissions/permissions') -exports.login = (credentials) -> +exports.login = (params) -> async.waterfall [ (callback) -> - if credentials? - return resin.auth.parseCredentials(credentials, callback) + if params.credentials? + return resin.auth.parseCredentials(params.credentials, callback) else return ui.widgets.login(callback) @@ -18,7 +19,7 @@ exports.login = (credentials) -> ], resin.errors.handle -exports.logout = -> +exports.logout = permissions.user -> resin.auth.logout() exports.signup = -> @@ -26,7 +27,7 @@ exports.signup = -> absUrl = url.resolve(resin.settings.get('remoteUrl'), signupUrl) open(absUrl) -exports.whoami = -> +exports.whoami = permissions.user -> resin.auth.whoami (error, username) -> resin.errors.handle(error) if error? diff --git a/lib/actions/device.coffee b/lib/actions/device.coffee index c89311cb..d52e8cbd 100644 --- a/lib/actions/device.coffee +++ b/lib/actions/device.coffee @@ -1,9 +1,9 @@ resin = require('../resin') -cli = require('../cli/cli') ui = require('../ui') +permissions = require('../permissions/permissions') -exports.list = (applicationId) -> - resin.models.device.getAllByApplication applicationId, (error, devices) -> +exports.list = permissions.user (params) -> + resin.models.device.getAllByApplication params.id, (error, devices) -> resin.errors.handle(error) if error? resin.log.out ui.widgets.table.horizontal devices, (device) -> @@ -16,8 +16,8 @@ exports.list = (applicationId) -> return device , [ 'ID', 'Name', 'Device Type', 'Is Online', 'IP Address', 'Application', 'Status', 'Last Seen' ] -exports.info = (deviceId) -> - resin.models.device.get deviceId, (error, device) -> +exports.info = permissions.user (params) -> + resin.models.device.get params.id, (error, device) -> resin.errors.handle(error) if error? resin.log.out ui.widgets.table.vertical device, (device) -> @@ -40,12 +40,11 @@ exports.info = (deviceId) -> 'Note' ] -exports.remove = (id) -> - confirmArgument = cli.getArgument('yes') - ui.patterns.remove 'device', confirmArgument, (callback) -> - resin.models.device.remove(id, callback) +exports.remove = permissions.user (params, options) -> + ui.patterns.remove 'device', options.yes, (callback) -> + resin.models.device.remove(params.id, callback) , resin.errors.handle -exports.identify = (uuid) -> - resin.models.device.identify uuid, (error) -> +exports.identify = permissions.user (params) -> + resin.models.device.identify params.uuid, (error) -> resin.errors.handle(error) if error? diff --git a/lib/actions/environment-variables.coffee b/lib/actions/environment-variables.coffee index 1bb9005d..99b308fc 100644 --- a/lib/actions/environment-variables.coffee +++ b/lib/actions/environment-variables.coffee @@ -1,29 +1,26 @@ _ = require('lodash') resin = require('../resin') -cli = require('../cli/cli') ui = require('../ui') +permissions = require('../permissions/permissions') SYSTEM_VAR_REGEX = /^RESIN_/ isSystemVariable = (environmentVariable) -> SYSTEM_VAR_REGEX.test(environmentVariable.name) -exports.list = -> - applicationId = cli.getArgument('application') - - if not applicationId? +exports.list = permissions.user (params, options) -> + if not options.application? resin.errors.handle(new Error('You have to specify an application')) - resin.models.environmentVariables.getAllByApplication applicationId, (error, environmentVariables) -> + resin.models.environmentVariables.getAllByApplication options.application, (error, environmentVariables) -> resin.errors.handle(error) if error? - if not cli.getArgument('verbose')? + if not options.verbose environmentVariables = _.reject(environmentVariables, isSystemVariable) resin.log.out(ui.widgets.table.horizontal(environmentVariables)) -exports.remove = (id) -> - confirmArgument = cli.getArgument('yes') - ui.patterns.remove 'environment variable', confirmArgument, (callback) -> - resin.models.environmentVariables.remove(id, callback) +exports.remove = permissions.user (params, options) -> + ui.patterns.remove 'environment variable', options.yes, (callback) -> + resin.models.environmentVariables.remove(params.id, callback) , resin.errors.handle diff --git a/lib/actions/help.coffee b/lib/actions/help.coffee new file mode 100644 index 00000000..139c2476 --- /dev/null +++ b/lib/actions/help.coffee @@ -0,0 +1,111 @@ +_ = require('lodash') +_.str = require('underscore.string') +resin = require('../resin') +capitano = require('capitano') + +# TODO: Refactor this terrible mess + +PADDING_INITIAL = ' ' +PADDING_MIDDLE = '\t' + +getFieldMaxLength = (array, field) -> + return _.max _.map array, (item) -> + return item[field].toString().length + +buildHelpString = (firstColumn, secondColumn) -> + result = "#{PADDING_INITIAL}#{firstColumn}" + result += "#{PADDING_MIDDLE}#{secondColumn}" + return result + +addOptionPrefix = (option) -> + return if option.length <= 0 + if option.length is 1 + return "-#{option}" + else + return "--#{option}" + +addAlias = (alias) -> + return ", #{addOptionPrefix(alias)}" + +buildOptionSignatureHelp = (option) -> + result = addOptionPrefix(option.signature.toString()) + + if _.isString(option.alias) + result += addAlias(option.alias) + else if _.isArray(option.alias) + for alias in option.alias + result += addAlias(alias) + + if option.parameter? + result += " <#{option.parameter}>" + + return result + +getCommandHelp = (command) -> + maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature') + commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ') + return buildHelpString(commandSignature, command.description) + +getOptionsParsedSignatures = (optionsHelp) -> + maxLength = _.max _.map optionsHelp, (signature) -> + return signature.length + + return _.map optionsHelp, (signature) -> + return _.str.rpad(signature, maxLength, ' ') + +getOptionHelp = (option, maxLength) -> + result = PADDING_INITIAL + result += _.str.rpad(option.signature, maxLength, ' ') + result += PADDING_MIDDLE + result += option.description + return result + +exports.general = -> + resin.log.out("Usage: #{process.argv[0]} [COMMAND] [OPTIONS]\n") + resin.log.out('Commands:\n') + + for command in capitano.state.commands + continue if command.isWildcard() + resin.log.out(getCommandHelp(command)) + + resin.log.out('\nGlobal Options:\n') + + options = _.map capitano.state.globalOptions, (option) -> + option.signature = buildOptionSignatureHelp(option) + return option + + optionSignatureMaxLength = _.max _.map options, (option) -> + return option.signature.length + + for option in options + resin.log.out(getOptionHelp(option, optionSignatureMaxLength)) + + resin.log.out() + +exports.command = (params) -> + command = capitano.state.getMatchCommand(params.command) + + if not command? or command.isWildcard() + return capitano.defaults.actions.commandNotFound(params.command) + + resin.log.out("Usage: #{command.signature}") + + if command.help? + resin.log.out("\n#{command.help}") + else if command.description? + resin.log.out("\n#{_.str.humanize(command.description)}") + + if not _.isEmpty(command.options) + resin.log.out('\nOptions:\n') + + options = _.map command.options, (option) -> + option.signature = buildOptionSignatureHelp(option) + return option + + optionSignatureMaxLength = _.max _.map options, (option) -> + return option.signature.toString().length + + for option in options + resin.log.out(getOptionHelp(option, optionSignatureMaxLength)) + + resin.log.out() diff --git a/lib/actions/index.coffee b/lib/actions/index.coffee index c90612ca..deff8fd5 100644 --- a/lib/actions/index.coffee +++ b/lib/actions/index.coffee @@ -7,3 +7,4 @@ module.exports = logs: require('./logs') preferences: require('./preferences') os: require('./os') + help: require('./help') diff --git a/lib/actions/keys.coffee b/lib/actions/keys.coffee index 4dd42f18..3567785f 100644 --- a/lib/actions/keys.coffee +++ b/lib/actions/keys.coffee @@ -2,10 +2,10 @@ _ = require('lodash') _.str = require('underscore.string') resin = require('../resin') helpers = require('../helpers/helpers') -cli = require('../cli/cli') ui = require('../ui') +permissions = require('../permissions/permissions') -exports.list = -> +exports.list = permissions.user -> resin.server.get resin.settings.get('urls.keys'), (error, response, keys) -> resin.errors.handle(error) if error? resin.log.out ui.widgets.table.horizontal keys, (key) -> @@ -13,24 +13,22 @@ exports.list = -> return key , [ 'ID', 'Title' ] -exports.info = (id) -> - id = _.parseInt(id) +exports.info = permissions.user (params) -> # TODO: We don't have a way to query a single ssh key yet. # As a workaround, we request all of them, and filter # the one we need. Fix once we have a better way. resin.server.get resin.settings.get('urls.keys'), (error, response, keys) -> resin.errors.handle(error) if error? - key = _.findWhere(keys, { id }) + key = _.findWhere(keys, id: params.id) if not key? - resin.errors.handle(new resin.errors.NotFound("key #{id}")) + resin.errors.handle(new resin.errors.NotFound("key #{params.id}")) - key.public_key = '\n' + _.chop(key.public_key, resin.settings.get('sshKeyWidth')).join('\n') + key.public_key = '\n' + _.str.chop(key.public_key, resin.settings.get('sshKeyWidth')).join('\n') resin.log.out(ui.widgets.table.vertical(key, _.identity, [ 'ID', 'Title', 'Public Key' ])) -exports.remove = (id) -> - confirmArgument = cli.getArgument('yes') - ui.patterns.remove 'key', confirmArgument, (callback) -> - url = _.template(resin.settings.get('urls.sshKey'), { id }) +exports.remove = permissions.user (params, options) -> + ui.patterns.remove 'key', options.yes, (callback) -> + url = _.template(resin.settings.get('urls.sshKey'), id: params.id) resin.server.delete(url, callback) , resin.errors.handle diff --git a/lib/actions/logs.coffee b/lib/actions/logs.coffee index 4f7e7b9f..f5d1a72d 100644 --- a/lib/actions/logs.coffee +++ b/lib/actions/logs.coffee @@ -2,7 +2,7 @@ _ = require('lodash') PubNub = require('pubnub') resin = require('../resin') helpers = require('../helpers/helpers') -cli = require('../cli/cli') +permissions = require('../permissions/permissions') LOGS_HISTORY_COUNT = 200 @@ -15,14 +15,15 @@ printLogs = (logs, number) -> logs = _.last(logs, number) if _.isNumber(number) resin.log.array(logs, resin.log.out) -exports.logs = (uuid) -> - numberOfLines = cli.getArgument('num', _.parseInt) - tailOutput = cli.getArgument('tail') or false +exports.logs = permissions.user (params, options) -> + + numberOfLines = options.num + tailOutput = options.tail or false if numberOfLines? and not _.isNumber(numberOfLines) resin.errors.handle(new Error('n/num should be a number')) - helpers.isDeviceUUIDValid uuid, (error, isValidUUID) -> + helpers.isDeviceUUIDValid params.uuid, (error, isValidUUID) -> resin.errors.handle(error) if error? if not isValidUUID @@ -33,7 +34,7 @@ exports.logs = (uuid) -> # all other actions from exiting on completion pubnub = PubNub.init(resin.settings.get('pubnub')) - channel = _.template(resin.settings.get('events.deviceLogs'), { uuid }) + channel = _.template(resin.settings.get('events.deviceLogs'), uuid: params.uuid) pubnub.history count: LOGS_HISTORY_COUNT diff --git a/lib/actions/os.coffee b/lib/actions/os.coffee index 8ef7347e..1aaad066 100644 --- a/lib/actions/os.coffee +++ b/lib/actions/os.coffee @@ -5,17 +5,17 @@ mkdirp = require('mkdirp') url = require('url') resin = require('../resin') connection = require('../connection/connection') -cli = require('../cli/cli') ui = require('../ui') +permissions = require('../permissions/permissions') -exports.download = (id) -> - params = - network: cli.getArgument('network') - wifiSsid: cli.getArgument('wifiSsid') - wifiKey: cli.getArgument('wifiKey') +exports.download = (params, options) -> + networkParams = + network: options.network + wifiSsid: options.ssid + wifiKey: options.key - fileName = resin.os.generateCacheName(id, params) - outputFile = cli.getArgument('output') or path.join(resin.settings.get('directories.os'), fileName) + fileName = resin.os.generateCacheName(params.id, networkParams) + outputFile = options.output or path.join(resin.settings.get('directories.os'), fileName) async.waterfall [ @@ -26,10 +26,10 @@ exports.download = (id) -> return callback(error) (callback) -> - connection.parseConnectionParameters(params, callback) + connection.parseConnectionParameters(networkParams, callback) (parameters, callback) -> - parameters.appId = id + parameters.appId = params.id query = url.format(query: parameters) downloadUrl = url.resolve(resin.settings.get('urls.download'), query) diff --git a/lib/actions/preferences.coffee b/lib/actions/preferences.coffee index c484f23c..a441baf8 100644 --- a/lib/actions/preferences.coffee +++ b/lib/actions/preferences.coffee @@ -1,8 +1,9 @@ open = require('open') url = require('url') resin = require('../resin') +permissions = require('../permissions/permissions') -exports.preferences = -> +exports.preferences = permissions.user -> preferencesUrl = resin.settings.get('urls.preferences') absUrl = url.resolve(resin.settings.get('remoteUrl'), preferencesUrl) open(absUrl) diff --git a/lib/app.coffee b/lib/app.coffee index fdf17ba4..e5fce8c1 100644 --- a/lib/app.coffee +++ b/lib/app.coffee @@ -1,152 +1,434 @@ _ = require('lodash') +capitano = require('capitano') resin = require('./resin') packageJSON = require('../package.json') actions = require('./actions') -cli = require('./cli/cli') -cli.setVersion(packageJSON.version) +capitano.command + signature: 'version' + description: 'output the version number' + action: -> + resin.log.out(packageJSON.version) + +capitano.command + signature: 'help [command...]' + description: 'show help' + action: (params) -> + if params.command? + actions.help.command(params) + else + actions.help.general() + +capitano.command + signature: '*' + action: -> + capitano.execute(command: 'help') # ---------- Options ---------- -cli.addOption - option: '-y, --yes' - description: 'confirm non interactively' - -cli.addOption - option: '-v, --verbose' - description: 'increase verbosity' - -cli.addOption - option: '-q, --quiet' +capitano.globalOption + signature: 'quiet' description: 'quiet (no output)' + boolean: true + alias: 'q' -cli.addOption - option: '-t, --type ' - description: 'specify a type when creating an application' - -cli.addOption - option: '-n, --num ' - description: 'number of lines to display' - -cli.addOption - option: '--tail' - description: 'continuously stream output' - -# TODO: I have to use 'application' instead of 'app' here -# as Commander gets confused with the app command -cli.addOption - option: '-a, --application ' - description: 'application id' - coerce: _.parseInt - -cli.addOption - option: '-w, --network ' - description: 'network type when downloading OS' - -cli.addOption - option: '-s, --wifi-ssid ' - description: 'wifi ssid, if network is wifi' - -cli.addOption - option: '-k, --wifi-key ' - description: 'wifi key, if network is wifi' - -cli.addOption - option: '-o, --output ' - description: 'output file' +yesOption = + signature: 'yes' + description: 'confirm non interactively' + boolean: true + alias: 'y' # ---------- Auth Module ---------- -cli.addCommand - command: 'login [username:password]' +capitano.command + signature: 'login [credentials]' description: 'login to resin.io' + help: ''' + Use this command to login to your resin.io account. + You need to login before you can use most of the commands this tool provides. + + You can pass your credentials as a colon separated string, or you can omit the + credentials, in which case the tool will present you with an interactive login form. + + Examples: + $ resin login username:password + $ resin login + ''' action: actions.auth.login -cli.addCommand - command: 'logout' +capitano.command + signature: 'logout' description: 'logout from resin.io' - action: actions.auth.logout - permission: 'user' + help: ''' + Use this command to logout from your resin.io account.o -cli.addCommand - command: 'signup' + Examples: + $ resin logout + ''' + action: actions.auth.logout + +capitano.command + signature: 'signup' description: 'signup to resin.io' + help: ''' + Use this command to signup for a resin.io account. + + In the future, this command may display a form in the terminal and handle + the registration purely from the command line, but for reasons of simplicity, + it opens your default web browser at the web based signup form. + + Examples: + $ resin signup + ''' action: actions.auth.signup -cli.addCommand - command: 'whoami' +capitano.command + signature: 'whoami' description: 'get current username' + help: ''' + Use this command to find out the current logged in username. + + Examples: + $ resin whoami + ''' action: actions.auth.whoami # ---------- App Module ---------- -cli.addResource - name: 'app' - displayName: 'application' - actions: actions.app - permission: 'user' +capitano.command + signature: 'app create ' + description: 'create an application' + help: ''' + Use this command to create a new resin.io application. -cli.addCommand - command: 'app:restart ' + You can specify the application type with the `--type` option. + Otherwise, an interactive dropdown will be shown for you to select from. + + TODO: We should support a command to list all supported device types. + + Examples: + $ resin app create MyApp + $ resin app create MyApp --type raspberry-pi + ''' + action: actions.app.create + options: [ + { + signature: 'type' + parameter: 'type' + description: 'application type' + alias: 't' + } + ] + +capitano.command + signature: 'apps' + description: 'list all applications' + help: ''' + Use this command to list all your applications. + + Notice this command only shows the most important bits of information for each app. + If you want detailed information, use resin app instead. + + Examples: + $ resin apps + ''' + action: actions.app.list + +capitano.command + signature: 'app ' + description: 'list a single application' + help: ''' + Use this command to show detailed information for a single application. + + Examples: + $ resin app 91 + ''' + action: actions.app.info + +capitano.command + signature: 'app rm ' + description: 'remove an application' + help: ''' + Use this command to remove a resin.io application. + + Notice this command asks for confirmation interactively. + You can avoid this by passing the `--yes` boolean option. + + Examples: + $ resin app rm 91 + $ resin app rm 91 --yes + ''' + action: actions.app.remove + options: [ yesOption ] + +capitano.command + signature: 'app restart ' description: 'restart an application' - action: actions.app.restart - permission: 'user' + help: ''' + Use this command to restart all devices that belongs to a certain application. -cli.addCommand - command: 'init ' + Examples: + $ resin app restart 91 + ''' + action: actions.app.restart + +capitano.command + signature: 'init ' description: 'init an application' + help: ''' + Use this command to associate a local project to an existing resin.io application. + + The application should be a git repository before issuing this command. + Notice this command adds a `resin` git remote to your application. + + Examples: + $ cd myApp && resin init 91 + ''' action: actions.app.init - permission: 'user' # ---------- Device Module ---------- -cli.addResource - name: 'device' - displayName: 'device' - actions: actions.device - permission: 'user' +capitano.command + signature: 'devices ' + description: 'list all devices' + help: ''' + Use this command to list all devices that belong to a certain application. -cli.addCommand - command: 'device:identify ' + Examples: + $ resin devices 91 + ''' + action: actions.device.list + +capitano.command + signature: 'device ' + description: 'list a single device' + help: ''' + Use this command to show information about a single device. + + Examples: + $ resin device 317 + ''' + action: actions.device.info + +capitano.command + signature: 'device rm ' + description: 'remove a device' + help: ''' + Use this command to remove a device from resin.io. + + Notice this command asks for confirmation interactively. + You can avoid this by passing the `--yes` boolean option. + + Examples: + $ resin device rm 317 + $ resin device rm 317 --yes + ''' + action: actions.device.remove + options: [ yesOption ] + +capitano.command + signature: 'device identify ' description: 'identify a device with a UUID' + help: ''' + Use this command to identify a device. + + In the Raspberry Pi, the ACT led is blinked several times. + + Examples: + $ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 + ''' action: actions.device.identify - permission: 'user' # ---------- Preferences Module ---------- -cli.addCommand - command: 'preferences' +capitano.command + signature: 'preferences' description: 'open preferences form' + help: ''' + Use this command to open the preferences form. + + In the future, we will allow changing all preferences directly from the terminal. + For now, we open your default web browser and point it to the web based preferences form. + + Examples: + $ resin preferences + ''' action: actions.preferences.preferences - permission: 'user' # ---------- Keys Module ---------- -cli.addResource - name: 'key' - displayName: 'ssh key' - actions: actions.keys - permission: 'user' +capitano.command + signature: 'keys' + description: 'list all ssh keys' + help: ''' + Use this command to list all your SSH keys. + + Examples: + $ resin keys + ''' + action: actions.keys.list + +capitano.command + signature: 'key ' + description: 'list a single ssh key' + help: ''' + Use this command to show information about a single SSH key. + + Examples: + $ resin key 17 + ''' + action: actions.keys.info + +capitano.command + signature: 'key rm ' + description: 'remove a ssh key' + help: ''' + Use this command to remove a SSH key from resin.io. + + Notice this command asks for confirmation interactively. + You can avoid this by passing the `--yes` boolean option. + + Examples: + $ resin key rm 17 + $ resin key rm 17 --yes + ''' + action: actions.keys.remove + options: [ yesOption ] # ---------- Env Module ---------- -cli.addResource - name: 'env' - displayName: 'environment variable' - actions: actions.env - permission: 'user' +capitano.command + signature: 'envs' + description: 'list all environment variables' + help: ''' + Use this command to list all environment variables for a particular application. + Notice we will support per-device environment variables soon. + + This command lists all custom environment variables set on the devices running + the application. If you want to see all environment variables, including private + ones used by resin, use the verbose option. + + Example: + $ resin envs --application 91 + $ resin envs --application 91 --verbose + ''' + action: actions.env.list + options: [ + { + signature: 'application' + parameter: 'application' + description: 'application id' + alias: [ 'a', 'app' ] + } + { + signature: 'verbose' + description: 'show private environment variables' + boolean: true + alias: 'v' + } + ] + +capitano.command + signature: 'env rm ' + description: 'remove an environment variable' + help: ''' + Use this command to remove an environment variable from an application. + + Don't remove resin specific variables, as things might not work as expected. + + Notice this command asks for confirmation interactively. + You can avoid this by passing the `--yes` boolean option. + + Examples: + $ resin env rm 215 + $ resin env rm 215 --yes + ''' + action: actions.env.remove + options: [ yesOption ] # ---------- Logs Module ---------- -cli.addCommand - command: 'logs ' +capitano.command + signature: 'logs ' description: 'show device logs' + help: ''' + Use this command to show logs for a specific device. + + By default, the command prints all log messages and exit. + + To limit the output to the n last lines, use the `--num` option along with a number. + This is similar to doing `resin logs | tail -n X`. + + To continuously stream output, and see new logs in real time, use the `--tail` option. + + Examples: + $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 + $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20 + $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail + ''' action: actions.logs.logs - permission: 'user' + options: [ + { + signature: 'num' + parameter: 'num' + description: 'number of lines to display' + alias: 'n' + } + { + signature: 'tail' + description: 'continuously stream output' + boolean: true + alias: 't' + } + ] # ---------- OS Module ---------- -cli.addCommand - command: 'os:download ' +capitano.command + signature: 'os download ' description: 'download device OS' + help: ''' + Use this command to download the device OS configured to a specific network. + + Ethernet: + You can setup the device OS to use ethernet by setting the `--network` option to "ethernet". + + Wifi: + You can setup the device OS to use wifi by setting the `--network` option to "wifi". + If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well. + + By default, this command saved the downloaded image into a resin specific directory. + You can save it to a custom location by specifying the `--output` option. + + Examples: + $ resin os download 91 --network ethernet + $ resin os download 91 --network wifi --ssid MyNetwork --key secreykey123 + $ resin os download 91 --network ethernet --output ~/MyResinOS.zip + ''' action: actions.os.download - permission: 'user' + options: [ + { + signature: 'network' + parameter: 'network' + description: 'network type' + alias: 'n' + } + { + signature: 'ssid' + parameter: 'ssid' + description: 'wifi ssid, if network is wifi' + alias: 's' + } + { + signature: 'key' + parameter: 'key' + description: 'wifi key, if network is wifi' + alias: 'k' + } + { + signature: 'output' + parameter: 'output' + description: 'output file' + alias: 'o' + } + ] + +cli = capitano.parse(process.argv) resin.data.prefix.set resin.settings.get('dataPrefix'), (error) -> resin.errors.handle(error) if error? - cli.parse(process.argv) + resin.log.setQuiet(cli.global.quiet) - quiet = cli.getArgument('quiet') - resin.log.setQuiet(quiet) + capitano.execute(cli) diff --git a/lib/cli/cli.coffee b/lib/cli/cli.coffee deleted file mode 100644 index 82b2ee90..00000000 --- a/lib/cli/cli.coffee +++ /dev/null @@ -1,101 +0,0 @@ -_ = require('lodash') -program = require('commander') -pluralize = require('pluralize') -indefiniteArticle = require('indefinite-article') -resin = require('../resin') -cliPermissions = require('./cli-permissions') - -exports.getArgument = (name, coerceFunction) -> - argument = program[name] - return if not argument? - - if _.isFunction(coerceFunction) - argument = coerceFunction(argument) - - return argument - -exports.setVersion = (version) -> - program.version(version) - - # Set version command automatically - exports.addCommand - command: 'version' - description: 'show version' - action: -> - resin.log.out(version) - -applyPermissions = (permission, action, onError) -> - permissionFunction = cliPermissions[permission] - if not _.isFunction(permissionFunction) - throw new Error("Invalid permission #{permission}") - return permissionFunction(action, onError) - -exports.addCommand = (options = {}) -> - - _.defaults options, - onError: resin.errors.handle - - if options.permission? - action = applyPermissions(options.permission, options.action, options.onError) - else - action = options.action - - program - .command(options.command) - .description(options.description) - .action(action) - - return program - -exports.addOption = (options = {}) -> - program.option(options.option, options.description, options.coerce) - -exports.addResource = (options = {}) -> - options.displayName ?= options.name - nameArticle = indefiniteArticle(options.displayName) - - pluralizedName = pluralize(options.name) - pluralizedDisplayName = pluralize(options.displayName) - - if _.isFunction(options.actions.create) - exports.addCommand - command: "#{options.name}:create " - description: "create #{nameArticle} #{options.displayName}" - action: options.actions.create - permission: options.permission - - if _.isFunction(options.actions.list) - exports.addCommand - command: "#{pluralizedName}" - description: "list all #{pluralizedDisplayName}" - action: options.actions.list - permission: options.permission - - if _.isFunction(options.actions.info) - exports.addCommand - command: "#{options.name} " - description: "list a single #{options.displayName}" - action: options.actions.info - permission: options.permission - - if _.isFunction(options.actions.remove) - exports.addCommand - command: "#{options.name}:rm " - description: "remove #{nameArticle} #{options.displayName}" - action: options.actions.remove - permission: options.permission - -exports.parse = (argv) -> - - # Matches unknown commands - exports.addCommand - command: '*' - description: '' - action: -> - program.outputHelp() - - program.parse(argv) - - # Show help if no command is passed - if _.isEmpty(program.args) - program.outputHelp() diff --git a/lib/cli/cli-permissions.coffee b/lib/permissions/permissions.coffee similarity index 83% rename from lib/cli/cli-permissions.coffee rename to lib/permissions/permissions.coffee index 93fd144a..34c9e7ad 100644 --- a/lib/cli/cli-permissions.coffee +++ b/lib/permissions/permissions.coffee @@ -10,6 +10,7 @@ exports.user = (fn, onError) -> if onError? return onError(error) else - throw error + console.error(error.message) + process.exit(1) fn.apply(null, args) diff --git a/lib/cli/cli-permissions.spec.coffee b/lib/permissions/permissions.spec.coffee similarity index 84% rename from lib/cli/cli-permissions.spec.coffee rename to lib/permissions/permissions.spec.coffee index 0c25c7f7..1068aef8 100644 --- a/lib/cli/cli-permissions.spec.coffee +++ b/lib/permissions/permissions.spec.coffee @@ -3,11 +3,11 @@ nock = require('nock') sinon = require('sinon') expect = require('chai').expect resin = require('../resin') -cliPermissions = require('./cli-permissions') +permissions = require('./permissions') johnDoeFixture = require('../../tests/fixtures/johndoe') mock = require('../../tests/utils/mock') -describe 'CLI Permissions:', -> +describe 'Permissions:', -> describe '#user()', -> @@ -31,14 +31,14 @@ describe 'CLI Permissions:', -> it 'should not call the function', (done) -> spy = sinon.spy() - cliPermissions.user(spy, _.noop)() + permissions.user(spy, _.noop)() _.defer -> expect(spy).to.not.have.been.called done() it 'it should call the second function with an error', (done) -> - func = cliPermissions.user _.noop, (error) -> + func = permissions.user _.noop, (error) -> expect(error).to.be.an.instanceof(Error) done() func() @@ -56,7 +56,7 @@ describe 'CLI Permissions:', -> args = [ 1, 2, 3, 'foo', 'bar' ] spy = sinon.spy() - cliPermissions.user(spy, _.noop).apply(null, args) + permissions.user(spy, _.noop).apply(null, args) _.defer -> expect(spy).to.have.been.calledWith(args...) @@ -64,7 +64,7 @@ describe 'CLI Permissions:', -> it 'should not call the second function', (done) -> spy = sinon.spy() - cliPermissions.user(_.noop, spy)() + permissions.user(_.noop, spy)() _.defer -> expect(spy).to.not.have.been.called diff --git a/package.json b/package.json index 090486d1..de234a77 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "lodash": "~2.4.1", "async": "~0.9.0", "rimraf": "~2.2.8", - "commander": "~2.5.0", "mkdirp": "~0.5.0", "pinejs-client-js": "git+ssh://git@bitbucket.org/rulemotion/pinejs-client-js.git", "bluebird": "~2.3.11", @@ -73,6 +72,7 @@ "lodash-contrib": "~241.4.14", "conf.js": "~0.1.1", "coffee-script": "~1.8.0", - "git-cli": "~0.8.2" + "git-cli": "~0.8.2", + "capitano": "~1.0.0" } }