Merge pull request #118 from resin-io/resin-cli-form

Integrate resin-cli-form
This commit is contained in:
Juan Cruz Viotti 2015-07-27 13:30:48 -04:00
commit d6ae689593
16 changed files with 438 additions and 131 deletions

View File

@ -1,5 +1,5 @@
(function() { (function() {
var _, async, commandOptions, path, resin, vcs, visuals; var _, async, commandOptions, form, path, resin, vcs, visuals;
path = require('path'); path = require('path');
@ -15,6 +15,8 @@
vcs = require('resin-vcs'); vcs = require('resin-vcs');
form = require('resin-cli-form');
exports.create = { exports.create = {
signature: 'app create <name>', signature: 'app create <name>',
description: 'create an application', description: 'create an application',
@ -39,7 +41,13 @@
if (options.type != null) { if (options.type != null) {
return callback(null, options.type); return callback(null, options.type);
} }
return visuals.patterns.selectDeviceType(callback); return resin.models.device.getSupportedDeviceTypes().then(function(supportedDeviceTypes) {
return form.ask({
message: 'Device Type',
type: 'list',
choices: supportedDeviceTypes
});
}).nodeify(callback);
}, function(type, callback) { }, function(type, callback) {
options.type = type; options.type = type;
return resin.models.application.create(params.name, options.type).nodeify(callback); return resin.models.application.create(params.name, options.type).nodeify(callback);
@ -92,9 +100,24 @@
options: [commandOptions.yes], options: [commandOptions.yes],
permission: 'user', permission: 'user',
action: function(params, options, done) { action: function(params, options, done) {
return visuals.patterns.remove('application', options.yes, function(callback) { return async.waterfall([
return resin.models.application.remove(params.name).nodeify(callback); function(callback) {
}, done); if (options.yes) {
return callback(null, true);
} else {
return form.ask({
message: 'Are you sure you want to delete the application?',
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) {
if (!confirmed) {
return callback();
}
return resin.models.application.remove(params.name).nodeify(callback);
}
], done);
} }
}; };
@ -116,7 +139,15 @@
return callback(new Error("Invalid application: " + params.name)); return callback(new Error("Invalid application: " + params.name));
} }
message = "Are you sure you want to associate " + currentDirectory + " with " + params.name + "?"; message = "Are you sure you want to associate " + currentDirectory + " with " + params.name + "?";
return visuals.patterns.confirm(options.yes, message, callback); if (options.yes) {
return callback(null, true);
} else {
return form.ask({
message: message,
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) { }, function(confirmed, callback) {
if (!confirmed) { if (!confirmed) {
return done(); return done();
@ -149,12 +180,11 @@
function(callback) { function(callback) {
var currentDirectoryBasename; var currentDirectoryBasename;
currentDirectoryBasename = path.basename(currentDirectory); currentDirectoryBasename = path.basename(currentDirectory);
return visuals.form.ask({ return form.ask({
label: 'What is the name of your application?', message: 'What is the name of your application?',
name: 'application', type: 'input',
type: 'text', "default": currentDirectoryBasename
value: currentDirectoryBasename }).nodeify(callback);
}, callback);
}, function(applicationName, callback) { }, function(applicationName, callback) {
return exports.create.action({ return exports.create.action({
name: applicationName name: applicationName

View File

@ -1,5 +1,5 @@
(function() { (function() {
var TOKEN_URL, _, async, open, resin, settings, url, visuals; var TOKEN_URL, _, async, form, open, resin, settings, url;
open = require('open'); open = require('open');
@ -13,7 +13,7 @@
settings = require('resin-settings-client'); settings = require('resin-settings-client');
visuals = require('resin-cli-visuals'); form = require('resin-cli-form');
TOKEN_URL = url.resolve(settings.get('dashboardUrl'), '/preferences'); TOKEN_URL = url.resolve(settings.get('dashboardUrl'), '/preferences');
@ -32,7 +32,10 @@
if (error != null) { if (error != null) {
console.error("Unable to open a web browser in the current environment.\nPlease visit " + TOKEN_URL + " manually."); console.error("Unable to open a web browser in the current environment.\nPlease visit " + TOKEN_URL + " manually.");
} }
return visuals.patterns.loginWithToken(callback); return form.ask({
message: 'What\'s your token? (visible in the preferences page)',
type: 'input'
}).nodeify(callback);
}); });
}, function(token, callback) { }, function(token, callback) {
return resin.auth.loginWithToken(token).nodeify(callback); return resin.auth.loginWithToken(token).nodeify(callback);
@ -97,7 +100,27 @@
if (hasOptionCredentials) { if (hasOptionCredentials) {
return callback(null, options); return callback(null, options);
} }
return visuals.patterns.register(callback); return form.run([
{
message: 'Email:',
name: 'email',
type: 'input'
}, {
message: 'Username:',
name: 'username',
type: 'input'
}, {
message: 'Password:',
name: 'password',
type: 'password',
validate: function(input) {
if (input.length < 8) {
return 'Password should be 8 characters long';
}
return true;
}
}
]).nodeify(callback);
}, function(credentials, callback) { }, function(credentials, callback) {
return resin.auth.register(credentials)["return"](credentials).nodeify(callback); return resin.auth.register(credentials)["return"](credentials).nodeify(callback);
}, function(credentials, callback) { }, function(credentials, callback) {

View File

@ -1,5 +1,5 @@
(function() { (function() {
var _, async, capitano, commandOptions, deviceConfig, fse, image, inject, manager, path, pine, registerDevice, resin, tmp, vcs, visuals; var _, async, capitano, commandOptions, deviceConfig, form, fse, image, inject, manager, path, pine, registerDevice, resin, tmp, vcs, visuals;
fse = require('fs-extra'); fse = require('fs-extra');
@ -31,6 +31,8 @@
deviceConfig = require('resin-device-config'); deviceConfig = require('resin-device-config');
form = require('resin-cli-form');
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
commandOptions = require('./command-options'); commandOptions = require('./command-options');
@ -80,9 +82,24 @@
options: [commandOptions.yes], options: [commandOptions.yes],
permission: 'user', permission: 'user',
action: function(params, options, done) { action: function(params, options, done) {
return visuals.patterns.remove('device', options.yes, function(callback) { return async.waterfall([
return resin.models.device.remove(params.uuid).nodeify(callback); function(callback) {
}, done); if (options.yes) {
return callback(null, true);
} else {
return form.ask({
message: 'Are you sure you want to delete the device?',
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) {
if (!confirmed) {
return callback();
}
return resin.models.device.remove(params.uuid).nodeify(callback);
}
], done);
} }
}; };
@ -107,11 +124,10 @@
if (!_.isEmpty(params.newName)) { if (!_.isEmpty(params.newName)) {
return callback(null, params.newName); return callback(null, params.newName);
} }
return visuals.form.ask({ return form.ask({
label: 'How do you want to name this device?', message: 'How do you want to name this device?',
name: 'device', type: 'input'
type: 'text' }).nodeify(callback);
}, callback);
}, function(newName, callback) { }, function(newName, callback) {
return resin.models.device.rename(params.uuid, newName).nodeify(callback); return resin.models.device.rename(params.uuid, newName).nodeify(callback);
} }
@ -194,12 +210,39 @@
if (params.device != null) { if (params.device != null) {
return callback(null, params.device); return callback(null, params.device);
} }
return visuals.patterns.selectDrive(callback); return drivelist.list(function(error, drives) {
if (error != null) {
return callback(error);
}
return async.reject(drives, drivelist.isSystem, function(removableDrives) {
if (_.isEmpty(removableDrives)) {
return callback(new Error('No available drives'));
}
return form.ask({
message: 'Drive',
type: 'list',
choices: _.map(removableDrives, function(item) {
return {
name: item.device + " (" + item.size + ") - " + item.description,
value: item.device
};
})
}).nodeify(callback);
});
});
}, function(device, callback) { }, function(device, callback) {
var message; var message;
params.device = device; params.device = device;
message = "This will completely erase " + params.device + ". Are you sure you want to continue?"; message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
return visuals.patterns.confirm(options.yes, message, callback); if (options.yes) {
return callback(null, true);
} else {
return form.ask({
message: message,
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) { }, function(confirmed, callback) {
if (!confirmed) { if (!confirmed) {
return done(); return done();
@ -207,13 +250,30 @@
if (networkOptions.network != null) { if (networkOptions.network != null) {
return callback(); return callback();
} }
return visuals.patterns.selectNetworkParameters(function(error, parameters) { return form.run([
if (error != null) { {
return callback(error); message: 'Network Type',
name: 'network',
type: 'list',
choices: ['ethernet', 'wifi']
}, {
message: 'Wifi Ssid',
name: 'wifiSsid',
type: 'input',
when: {
network: 'wifi'
}
}, {
message: 'Wifi Key',
name: 'wifiKey',
type: 'input',
when: {
network: 'wifi'
}
} }
_.extend(networkOptions, parameters); ]).then(function(parameters) {
return callback(); return _.extend(networkOptions, parameters);
}); }).nodeify(callback);
}, function(callback) { }, function(callback) {
console.info("Checking application: " + options.application); console.info("Checking application: " + options.application);
return resin.models.application.get(options.application).nodeify(callback); return resin.models.application.get(options.application).nodeify(callback);

View File

@ -54,13 +54,28 @@
options: [commandOptions.yes, commandOptions.booleanDevice], options: [commandOptions.yes, commandOptions.booleanDevice],
permission: 'user', permission: 'user',
action: function(params, options, done) { action: function(params, options, done) {
return visuals.patterns.remove('environment variable', options.yes, function(callback) { return async.waterfall([
if (options.device) { function(callback) {
return resin.models.environmentVariables.device.remove(params.id).nodeify(callback); if (options.yes) {
} else { return callback(null, true);
return resin.models.environmentVariables.remove(params.id).nodeify(callback); } else {
return form.ask({
message: 'Are you sure you want to delete the environment variable?',
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) {
if (!confirmed) {
return callback();
}
if (options.device) {
return resin.models.environmentVariables.device.remove(params.id).nodeify(callback);
} else {
return resin.models.environmentVariables.remove(params.id).nodeify(callback);
}
} }
}, done); ], done);
} }
}; };

View File

@ -1,5 +1,5 @@
(function() { (function() {
var SSH_KEY_WIDTH, _, async, capitano, commandOptions, fs, resin, visuals; var SSH_KEY_WIDTH, _, async, capitano, commandOptions, form, fs, resin, visuals;
_ = require('lodash'); _ = require('lodash');
@ -17,6 +17,8 @@
commandOptions = require('./command-options'); commandOptions = require('./command-options');
form = require('resin-cli-form');
exports.list = { exports.list = {
signature: 'keys', signature: 'keys',
description: 'list all ssh keys', description: 'list all ssh keys',
@ -50,9 +52,24 @@
options: [commandOptions.yes], options: [commandOptions.yes],
permission: 'user', permission: 'user',
action: function(params, options, done) { action: function(params, options, done) {
return visuals.patterns.remove('key', options.yes, function(callback) { return async.waterfall([
return resin.models.key.remove(params.id).nodeify(callback); function(callback) {
}, done); if (options.yes) {
return callback(null, true);
} else {
return form.ask({
message: 'Are you sure you want to delete the key?',
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) {
if (!confirmed) {
return callback();
}
return resin.models.key.remove(params.id).nodeify(callback);
}
], done);
} }
}; };

View File

@ -1,5 +1,5 @@
(function() { (function() {
var _, commandOptions, plugins, visuals; var _, async, commandOptions, form, plugins, visuals;
_ = require('lodash'); _ = require('lodash');
@ -9,6 +9,10 @@
plugins = require('../plugins'); plugins = require('../plugins');
form = require('resin-cli-form');
async = require('async');
exports.list = { exports.list = {
signature: 'plugins', signature: 'plugins',
description: 'list all plugins', description: 'list all plugins',
@ -68,15 +72,30 @@
options: [commandOptions.yes], options: [commandOptions.yes],
permission: 'user', permission: 'user',
action: function(params, options, done) { action: function(params, options, done) {
return visuals.patterns.remove('plugin', options.yes, function(callback) { return async.waterfall([
return plugins.remove(params.name, callback); function(callback) {
}, function(error) { if (options.yes) {
if (error != null) { return callback(null, true);
return done(error); } else {
return form.ask({
message: 'Are you sure you want to delete the plugin?',
type: 'confirm',
"default": false
}).nodeify(callback);
}
}, function(confirmed, callback) {
if (!confirmed) {
return callback();
}
return plugins.remove(params.name, callback);
}, function(error) {
if (error != null) {
return done(error);
}
console.info("Plugin removed: " + params.name);
return done();
} }
console.info("Plugin removed: " + params.name); ]);
return done();
});
} }
}; };

View File

@ -5,6 +5,7 @@ resin = require('resin-sdk')
visuals = require('resin-cli-visuals') visuals = require('resin-cli-visuals')
commandOptions = require('./command-options') commandOptions = require('./command-options')
vcs = require('resin-vcs') vcs = require('resin-vcs')
form = require('resin-cli-form')
exports.create = exports.create =
signature: 'app create <name>' signature: 'app create <name>'
@ -34,7 +35,7 @@ exports.create =
] ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
async.waterfall([ async.waterfall [
(callback) -> (callback) ->
resin.models.application.has(params.name).nodeify(callback) resin.models.application.has(params.name).nodeify(callback)
@ -44,7 +45,12 @@ exports.create =
return callback(new Error('You already have an application with that name!')) return callback(new Error('You already have an application with that name!'))
return callback(null, options.type) if options.type? return callback(null, options.type) if options.type?
visuals.patterns.selectDeviceType(callback) resin.models.device.getSupportedDeviceTypes().then (supportedDeviceTypes) ->
form.ask
message: 'Device Type'
type: 'list'
choices: supportedDeviceTypes
.nodeify(callback)
(type, callback) -> (type, callback) ->
options.type = type options.type = type
@ -54,7 +60,7 @@ exports.create =
console.info("Application created: #{params.name} (#{options.type}, id #{applicationId})") console.info("Application created: #{params.name} (#{options.type}, id #{applicationId})")
return callback() return callback()
], done) ], done
exports.list = exports.list =
signature: 'apps' signature: 'apps'
@ -134,9 +140,22 @@ exports.remove =
options: [ commandOptions.yes ] options: [ commandOptions.yes ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
visuals.patterns.remove 'application', options.yes, (callback) -> async.waterfall [
resin.models.application.remove(params.name).nodeify(callback)
, done (callback) ->
if options.yes
return callback(null, true)
else
form.ask
message: 'Are you sure you want to delete the application?'
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) ->
return callback() if not confirmed
resin.models.application.remove(params.name).nodeify(callback)
], done
exports.associate = exports.associate =
signature: 'app associate <name>' signature: 'app associate <name>'
@ -169,7 +188,14 @@ exports.associate =
return callback(new Error("Invalid application: #{params.name}")) return callback(new Error("Invalid application: #{params.name}"))
message = "Are you sure you want to associate #{currentDirectory} with #{params.name}?" message = "Are you sure you want to associate #{currentDirectory} with #{params.name}?"
visuals.patterns.confirm(options.yes, message, callback) if options.yes
return callback(null, true)
else
form.ask
message: message
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) -> (confirmed, callback) ->
return done() if not confirmed return done() if not confirmed
@ -207,16 +233,15 @@ exports.init =
currentDirectory = process.cwd() currentDirectory = process.cwd()
async.waterfall([ async.waterfall [
(callback) -> (callback) ->
currentDirectoryBasename = path.basename(currentDirectory) currentDirectoryBasename = path.basename(currentDirectory)
visuals.form.ask form.ask
label: 'What is the name of your application?' message: 'What is the name of your application?'
name: 'application' type: 'input'
type: 'text' default: currentDirectoryBasename
value: currentDirectoryBasename .nodeify(callback)
, callback
(applicationName, callback) -> (applicationName, callback) ->
@ -229,4 +254,4 @@ exports.init =
(applicationName, callback) -> (applicationName, callback) ->
exports.associate.action(name: applicationName, options, callback) exports.associate.action(name: applicationName, options, callback)
], done) ], done

View File

@ -4,7 +4,7 @@ url = require('url')
async = require('async') async = require('async')
resin = require('resin-sdk') resin = require('resin-sdk')
settings = require('resin-settings-client') settings = require('resin-settings-client')
visuals = require('resin-cli-visuals') form = require('resin-cli-form')
TOKEN_URL = url.resolve(settings.get('dashboardUrl'), '/preferences') TOKEN_URL = url.resolve(settings.get('dashboardUrl'), '/preferences')
@ -25,7 +25,7 @@ exports.login =
""" """
action: (params, options, done) -> action: (params, options, done) ->
async.waterfall([ async.waterfall [
(callback) -> (callback) ->
return callback(null, params.token) if params.token? return callback(null, params.token) if params.token?
@ -44,7 +44,10 @@ exports.login =
Please visit #{TOKEN_URL} manually. Please visit #{TOKEN_URL} manually.
""" """
visuals.patterns.loginWithToken(callback) form.ask
message: 'What\'s your token? (visible in the preferences page)'
type: 'input'
.nodeify(callback)
(token, callback) -> (token, callback) ->
resin.auth.loginWithToken(token).nodeify(callback) resin.auth.loginWithToken(token).nodeify(callback)
@ -56,7 +59,7 @@ exports.login =
console.info("Successfully logged in as: #{username}") console.info("Successfully logged in as: #{username}")
return callback() return callback()
], done) ], done
exports.logout = exports.logout =
signature: 'logout' signature: 'logout'
@ -127,11 +130,29 @@ exports.signup =
if not options.password? if not options.password?
return done(new Error('Missing password')) return done(new Error('Missing password'))
async.waterfall([ async.waterfall [
(callback) -> (callback) ->
return callback(null, options) if hasOptionCredentials return callback(null, options) if hasOptionCredentials
visuals.patterns.register(callback) form.run [
message: 'Email:'
name: 'email'
type: 'input'
,
message: 'Username:'
name: 'username'
type: 'input'
,
message: 'Password:'
name: 'password'
type: 'password',
validate: (input) ->
if input.length < 8
return 'Password should be 8 characters long'
return true
]
.nodeify(callback)
(credentials, callback) -> (credentials, callback) ->
resin.auth.register(credentials).return(credentials).nodeify(callback) resin.auth.register(credentials).return(credentials).nodeify(callback)
@ -139,7 +160,7 @@ exports.signup =
(credentials, callback) -> (credentials, callback) ->
resin.auth.login(credentials).nodeify(callback) resin.auth.login(credentials).nodeify(callback)
], done) ], done
exports.whoami = exports.whoami =
signature: 'whoami' signature: 'whoami'

View File

@ -13,6 +13,7 @@ registerDevice = require('resin-register-device')
pine = require('resin-pine') pine = require('resin-pine')
tmp = require('tmp') tmp = require('tmp')
deviceConfig = require('resin-device-config') deviceConfig = require('resin-device-config')
form = require('resin-cli-form')
# Cleanup the temporary files even when an uncaught exception occurs # Cleanup the temporary files even when an uncaught exception occurs
tmp.setGracefulCleanup() tmp.setGracefulCleanup()
@ -110,9 +111,22 @@ exports.remove =
options: [ commandOptions.yes ] options: [ commandOptions.yes ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
visuals.patterns.remove 'device', options.yes, (callback) -> async.waterfall [
resin.models.device.remove(params.uuid).nodeify(callback)
, done (callback) ->
if options.yes
return callback(null, true)
else
form.ask
message: 'Are you sure you want to delete the device?'
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) ->
return callback() if not confirmed
resin.models.device.remove(params.uuid).nodeify(callback)
], done
exports.identify = exports.identify =
signature: 'device identify <uuid>' signature: 'device identify <uuid>'
@ -151,11 +165,10 @@ exports.rename =
if not _.isEmpty(params.newName) if not _.isEmpty(params.newName)
return callback(null, params.newName) return callback(null, params.newName)
visuals.form.ask form.ask
label: 'How do you want to name this device?' message: 'How do you want to name this device?'
name: 'device' type: 'input'
type: 'text' .nodeify(callback)
, callback
(newName, callback) -> (newName, callback) ->
resin.models.device.rename(params.uuid, newName).nodeify(callback) resin.models.device.rename(params.uuid, newName).nodeify(callback)
@ -263,7 +276,7 @@ exports.init =
wifiSsid: options.ssid wifiSsid: options.ssid
wifiKey: options.key wifiKey: options.key
async.waterfall([ async.waterfall [
(callback) -> (callback) ->
return callback(null, options.application) if options.application? return callback(null, options.application) if options.application?
@ -278,20 +291,60 @@ exports.init =
return callback(new Error("Invalid application: #{options.application}")) return callback(new Error("Invalid application: #{options.application}"))
return callback(null, params.device) if params.device? return callback(null, params.device) if params.device?
visuals.patterns.selectDrive(callback) drivelist.list (error, drives) ->
return callback(error) if error?
async.reject drives, drivelist.isSystem, (removableDrives) ->
if _.isEmpty(removableDrives)
return callback(new Error('No available drives'))
form.ask
message: 'Drive'
type: 'list'
choices: _.map removableDrives, (item) ->
return {
name: "#{item.device} (#{item.size}) - #{item.description}"
value: item.device
}
.nodeify(callback)
(device, callback) -> (device, callback) ->
params.device = device params.device = device
message = "This will completely erase #{params.device}. Are you sure you want to continue?" message = "This will completely erase #{params.device}. Are you sure you want to continue?"
visuals.patterns.confirm(options.yes, message, callback) if options.yes
return callback(null, true)
else
form.ask
message: message
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) -> (confirmed, callback) ->
return done() if not confirmed return done() if not confirmed
return callback() if networkOptions.network? return callback() if networkOptions.network?
visuals.patterns.selectNetworkParameters (error, parameters) -> form.run [
return callback(error) if error? message: 'Network Type'
name: 'network'
type: 'list'
choices: [ 'ethernet', 'wifi' ]
,
message: 'Wifi Ssid'
name: 'wifiSsid'
type: 'input'
when:
network: 'wifi'
,
message: 'Wifi Key'
name: 'wifiKey'
type: 'input'
when:
network: 'wifi'
]
.then (parameters) ->
_.extend(networkOptions, parameters) _.extend(networkOptions, parameters)
return callback() .nodeify(callback)
(callback) -> (callback) ->
console.info("Checking application: #{options.application}") console.info("Checking application: #{options.application}")
@ -366,4 +419,4 @@ exports.init =
console.info("Device created: #{device.name}") console.info("Device created: #{device.name}")
return callback(null, device.name) return callback(null, device.name)
], done) ], done

View File

@ -34,7 +34,7 @@ exports.list =
] ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
async.waterfall([ async.waterfall [
(callback) -> (callback) ->
if options.application? if options.application?
@ -58,7 +58,7 @@ exports.list =
return callback() return callback()
], done) ], done
exports.remove = exports.remove =
signature: 'env rm <id>' signature: 'env rm <id>'
@ -85,12 +85,25 @@ exports.remove =
] ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
visuals.patterns.remove 'environment variable', options.yes, (callback) -> async.waterfall [
if options.device
resin.models.environmentVariables.device.remove(params.id).nodeify(callback) (callback) ->
else if options.yes
resin.models.environmentVariables.remove(params.id).nodeify(callback) return callback(null, true)
, done else
form.ask
message: 'Are you sure you want to delete the environment variable?'
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) ->
return callback() if not confirmed
if options.device
resin.models.environmentVariables.device.remove(params.id).nodeify(callback)
else
resin.models.environmentVariables.remove(params.id).nodeify(callback)
], done
exports.add = exports.add =
signature: 'env add <key> [value]' signature: 'env add <key> [value]'

View File

@ -6,6 +6,7 @@ resin = require('resin-sdk')
capitano = require('capitano') capitano = require('capitano')
visuals = require('resin-cli-visuals') visuals = require('resin-cli-visuals')
commandOptions = require('./command-options') commandOptions = require('./command-options')
form = require('resin-cli-form')
exports.list = exports.list =
signature: 'keys' signature: 'keys'
@ -58,9 +59,22 @@ exports.remove =
options: [ commandOptions.yes ] options: [ commandOptions.yes ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
visuals.patterns.remove 'key', options.yes, (callback) -> async.waterfall [
resin.models.key.remove(params.id).nodeify(callback)
, done (callback) ->
if options.yes
return callback(null, true)
else
form.ask
message: 'Are you sure you want to delete the key?'
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) ->
return callback() if not confirmed
resin.models.key.remove(params.id).nodeify(callback)
], done
exports.add = exports.add =
signature: 'key add <name> [path]' signature: 'key add <name> [path]'

View File

@ -2,6 +2,8 @@ _ = require('lodash')
visuals = require('resin-cli-visuals') visuals = require('resin-cli-visuals')
commandOptions = require('./command-options') commandOptions = require('./command-options')
plugins = require('../plugins') plugins = require('../plugins')
form = require('resin-cli-form')
async = require('async')
exports.list = exports.list =
signature: 'plugins' signature: 'plugins'
@ -86,9 +88,23 @@ exports.remove =
options: [ commandOptions.yes ] options: [ commandOptions.yes ]
permission: 'user' permission: 'user'
action: (params, options, done) -> action: (params, options, done) ->
visuals.patterns.remove 'plugin', options.yes, (callback) -> async.waterfall [
plugins.remove(params.name, callback)
(callback) ->
if options.yes
return callback(null, true)
else
form.ask
message: 'Are you sure you want to delete the plugin?'
type: 'confirm'
default: false
.nodeify(callback)
(confirmed, callback) ->
return callback() if not confirmed
plugins.remove(params.name, callback)
, (error) -> , (error) ->
return done(error) if error? return done(error) if error?
console.info("Plugin removed: #{params.name}") console.info("Plugin removed: #{params.name}")
return done() return done()
]

View File

@ -3,9 +3,9 @@
\fBresin\fR \- tab completion for resin \fBresin\fR \- tab completion for resin
.SH DESCRIPTION .SH DESCRIPTION
.P .P
It provides basic completion capabilities for \fBzsh\fR and \fBbash\fR\|\. It provides basic completion capabilities for \fBzsh\fP and \fBbash\fP\|\.
.P .P
If you're using \fBbash\fR, add the following line to your \fB~/\.bashrc\fR: If you're using \fBbash\fP, add the following line to your \fB~/\.bashrc\fP:
.P .P
.RS 2 .RS 2
.nf .nf
@ -29,7 +29,7 @@ ln \- /path/to/resin/completion/resin\.sh /usr/local/etc/bash\-completion\.d/res
.fi .fi
.RE .RE
.P .P
If you're using \fBzsh\fR, add the following to your \fB~/\.zshrc\fR: If you're using \fBzsh\fP, add the following to your \fB~/\.zshrc\fP:
.P .P
.RS 2 .RS 2
.nf .nf
@ -40,9 +40,9 @@ source /path/to/resin/completion/resin\.sh
.RE .RE
.SH RESIN PATH .SH RESIN PATH
.P .P
\fB/path/to/resin\fR refers to the place where resin was globally installed in your system\. \fB/path/to/resin\fP refers to the place where resin was globally installed in your system\.
.P .P
This is usually something like \fB/usr/lib/node_modules/resin\fR, or \fBC:\\Users\\AppData\\Roaming\\npm\\node_modules\\resin\fR on Windows\. This is usually something like \fB/usr/lib/node_modules/resin\fP, or \fBC:\\Users\\AppData\\Roaming\\npm\\node_modules\\resin\fP on Windows\.
.SH COPYRIGHT .SH COPYRIGHT
.P .P
resin is Copyright (C) 2014 Resin\.io https://resin\.io resin is Copyright (C) 2014 Resin\.io https://resin\.io

View File

@ -3,18 +3,18 @@
\fBresin-plugins\fR \- Creating Resin CLI plugins \fBresin-plugins\fR \- Creating Resin CLI plugins
.SH DESCRIPTION .SH DESCRIPTION
.P .P
Resin CLI plugins are managed by NPM\. Installing an NPM module that starts with \fBresin\-plugin\-*\fR globally will automatically make it available to the Resin CLI\. Resin CLI plugins are managed by NPM\. Installing an NPM module that starts with \fBresin\-plugin\-*\fP globally will automatically make it available to the Resin CLI\.
.SH TUTORIAL .SH TUTORIAL
.P .P
In this guide, we'll create a simple hello plugin that greets the user\. In this guide, we'll create a simple hello plugin that greets the user\.
.P .P
Create a directory called \fBresin\-plugin\-hello\fR, containing a single \fBindex\.js\fR file\. Create a directory called \fBresin\-plugin\-hello\fP, containing a single \fBindex\.js\fP file\.
.P .P
Within the new project, run \fBnpm init\fR and make sure the package name is set to \fBresin\-plugin\-hello\fR as well\. Within the new project, run \fBnpm init\fP and make sure the package name is set to \fBresin\-plugin\-hello\fP as well\.
.P .P
Also make sure that you have a \fBmain\fR field in \fBpackage\.json\fR that points to the \fBindex\.js\fR file you created above\. Also make sure that you have a \fBmain\fP field in \fBpackage\.json\fP that points to the \fBindex\.js\fP file you created above\.
.P .P
Your \fBpackage\.json\fR should look something like this: Your \fBpackage\.json\fP should look something like this:
.P .P
.RS 2 .RS 2
.nf .nf
@ -30,26 +30,26 @@ Your \fBpackage\.json\fR should look something like this:
.P .P
Your index file should export an object (if exposing a single command) or an array of objects (if exposing multiple commands)\. Your index file should export an object (if exposing a single command) or an array of objects (if exposing multiple commands)\.
.P .P
Notice that is very important that your \fBpackage\.json\fR \fBmain\fR field points to the file that is exporting the commands for the plugin to work correctly\. Notice that is very important that your \fBpackage\.json\fP \fBmain\fP field points to the file that is exporting the commands for the plugin to work correctly\.
.P .P
Each object describes a single command\. The accepted fields are: Each object describes a single command\. The accepted fields are:
.RS 0 .RS 0
.IP \(bu 2 .IP \(bu 2
\fBsignature\fR: A Capitano \fIhttps://github\.com/resin\-io/capitano\fR signature\. \fBsignature\fP: A Capitano \fIhttps://github\.com/resin\-io/capitano\fR signature\.
.IP \(bu 2 .IP \(bu 2
\fBdescription\fR: A string containing a short description of the command\. This will be shown on the Resin general help\. \fBdescription\fP: A string containing a short description of the command\. This will be shown on the Resin general help\.
.IP \(bu 2 .IP \(bu 2
\fBhelp\fR: A string containing an usage help page\. This will be shown when passing the signature to the \fBhelp\fR command\. \fBhelp\fP: A string containing an usage help page\. This will be shown when passing the signature to the \fBhelp\fP command\.
.IP \(bu 2 .IP \(bu 2
\fBaction\fR: A function that defines the action to take when the command is matched\. The function will be given 3 arguments (\fBparams\fR, \fBoptions\fR, \fBdone\fR)\. \fBaction\fP: A function that defines the action to take when the command is matched\. The function will be given 3 arguments (\fBparams\fP, \fBoptions\fP, \fBdone\fP)\.
.IP \(bu 2 .IP \(bu 2
\fBpermission\fR: A string describing the required permissions to run the command\. \fBpermission\fP: A string describing the required permissions to run the command\.
.IP \(bu 2 .IP \(bu 2
\fBoptions\fR: An array of Capitano \fIhttps://github\.com/resin\-io/capitano\fR options\. \fBoptions\fP: An array of Capitano \fIhttps://github\.com/resin\-io/capitano\fR options\.
.RE .RE
.P .P
The \fBindex\.js\fR file should look something like: The \fBindex\.js\fP file should look something like:
.P .P
.RS 2 .RS 2
.nf .nf
@ -83,7 +83,7 @@ module\.exports = {
.fi .fi
.RE .RE
.P .P
This example will register a \fBhello\fR command which requires a \fBname\fR parameter, and greets the user in result\. This example will register a \fBhello\fP command which requires a \fBname\fP parameter, and greets the user in result\.
.P .P
To test the plugin, first create a global link by running the following command inside your plugin directory: To test the plugin, first create a global link by running the following command inside your plugin directory:
.P .P
@ -93,7 +93,7 @@ $ npm link
.fi .fi
.RE .RE
.P .P
Now if you run \fB$ resin help\fR you should see your new command at the bottom of the list\. Now if you run \fB$ resin help\fP you should see your new command at the bottom of the list\.
.P .P
Try it out: Try it out:
.P .P
@ -105,9 +105,9 @@ Hey there Juan!
.RE .RE
.SH DONE CALLBACK .SH DONE CALLBACK
.P .P
It's very important that you call the \fBdone()\fR callback after your action finishes\. If you pass an \fBError\fR instance to \fBdone()\fR, its message will be displayed by the Resin CLI, exiting with an error code 1\. It's very important that you call the \fBdone()\fP callback after your action finishes\. If you pass an \fBError\fP instance to \fBdone()\fP, its message will be displayed by the Resin CLI, exiting with an error code 1\.
.P .P
If your action is synchronous and doesn't return any error, you can omit the \fBdone()\fR callback all together\. For example: If your action is synchronous and doesn't return any error, you can omit the \fBdone()\fP callback all together\. For example:
.P .P
.RS 2 .RS 2
.nf .nf
@ -123,9 +123,9 @@ module\.exports = {
.RE .RE
.SH PERMISSIONS .SH PERMISSIONS
.P .P
You can set a command permission to restrict access to the commands\. Currently, the only registered permission is \fBuser\fR, which requires the user to log in to Resin from the CLI\. You can set a command permission to restrict access to the commands\. Currently, the only registered permission is \fBuser\fP, which requires the user to log in to Resin from the CLI\.
.P .P
To require the user to login before calling our hello plugin, we can add \fBpermission: 'user'\fR to the command description: To require the user to login before calling our hello plugin, we can add \fBpermission: 'user'\fP to the command description:
.P .P
.RS 2 .RS 2
.nf .nf
@ -155,7 +155,7 @@ $ resin hello Juan \-\-language spanish
.fi .fi
.RE .RE
.P .P
We first need to register the \fBlanguage option\fR: We first need to register the \fBlanguage option\fP:
.P .P
.RS 2 .RS 2
.nf .nf
@ -184,10 +184,10 @@ module\.exports = {
.fi .fi
.RE .RE
.P .P
Here, we declared an option with a signature of \fBlanguage\fR (so we can use it as \fB\-\-language\fR), a parameter name of \fBlanguage\fR as well (this means we'll be able to access the option as the \fBlanguage\fR key: \fBoptions\.language\fR), a nice description and an alias \fBl\fR (which means we can use \fB\-l <language>\fR too)\. Here, we declared an option with a signature of \fBlanguage\fP (so we can use it as \fB\-\-language\fP), a parameter name of \fBlanguage\fP as well (this means we'll be able to access the option as the \fBlanguage\fP key: \fBoptions\.language\fP), a nice description and an alias \fBl\fP (which means we can use \fB\-l <language>\fP too)\.
.SH COFFEESCRIPT .SH COFFEESCRIPT
.P .P
We have CoffeeScript support out of the box\. Implement your commands in \fBindex\.coffee\fR and point \fBpackage\.json\fR \fBmain\fR to that file\. We have CoffeeScript support out of the box\. Implement your commands in \fBindex\.coffee\fP and point \fBpackage\.json\fP \fBmain\fP to that file\.
.SH RESIN\-SDK .SH RESIN\-SDK
.P .P
You can use the Resin SDK NodeJS module within your own plugins to communicate with Resin\. You can use the Resin SDK NodeJS module within your own plugins to communicate with Resin\.

View File

@ -3,7 +3,7 @@
\fBresin\fR \- command line tool to interact with resin\.io \fBresin\fR \- command line tool to interact with resin\.io
.SH SYNOPSIS .SH SYNOPSIS
.P .P
\fBresin\fR [options] <command> \fBresin\fP [options] <command>
.SH DESCRIPTION .SH DESCRIPTION
.P .P
\fBresin\fR is a powerful command line application to interact, develop and deploy resin\.io applications and devices\. \fBresin\fR is a powerful command line application to interact, develop and deploy resin\.io applications and devices\.

View File

@ -59,7 +59,8 @@
"nplugm": "^2.2.0", "nplugm": "^2.2.0",
"npm": "^2.13.0", "npm": "^2.13.0",
"open": "0.0.5", "open": "0.0.5",
"resin-cli-visuals": "^0.3.3", "resin-cli-form": "^1.1.0",
"resin-cli-visuals": "^1.0.0",
"resin-config-inject": "^2.0.0", "resin-config-inject": "^2.0.0",
"resin-device-config": "^1.0.0", "resin-device-config": "^1.0.0",
"resin-image": "^1.1.3", "resin-image": "^1.1.3",