mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-22 18:12:54 +00:00
Merge pull request #188 from resin-io/jviotti/feature/device-specs
Implement device specs. Fix #99
This commit is contained in:
commit
3c0acaa7da
@ -1,5 +1,5 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var _, commandOptions, events, helpers, resin, vcs, visuals;
|
var _, commandOptions, events, helpers, patterns, resin, vcs, visuals;
|
||||||
|
|
||||||
_ = require('lodash');
|
_ = require('lodash');
|
||||||
|
|
||||||
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
helpers = require('../utils/helpers');
|
helpers = require('../utils/helpers');
|
||||||
|
|
||||||
|
patterns = require('../utils/patterns');
|
||||||
|
|
||||||
exports.create = {
|
exports.create = {
|
||||||
signature: 'app create <name>',
|
signature: 'app create <name>',
|
||||||
description: 'create an application',
|
description: 'create an application',
|
||||||
@ -33,7 +35,7 @@
|
|||||||
if (hasApplication) {
|
if (hasApplication) {
|
||||||
throw new Error('You already have an application with that name!');
|
throw new Error('You already have an application with that name!');
|
||||||
}
|
}
|
||||||
}).then(helpers.selectDeviceType).then(function(deviceType) {
|
}).then(patterns.selectDeviceType).then(function(deviceType) {
|
||||||
return resin.models.application.create(params.name, deviceType);
|
return resin.models.application.create(params.name, deviceType);
|
||||||
}).then(function(application) {
|
}).then(function(application) {
|
||||||
console.info("Application created: " + application.app_name + " (" + application.device_type + ", id " + application.id + ")");
|
console.info("Application created: " + application.app_name + " (" + application.device_type + ", id " + application.id + ")");
|
||||||
@ -88,7 +90,7 @@
|
|||||||
options: [commandOptions.yes],
|
options: [commandOptions.yes],
|
||||||
permission: 'user',
|
permission: 'user',
|
||||||
action: function(params, options, done) {
|
action: function(params, options, done) {
|
||||||
return helpers.confirm(options.yes, 'Are you sure you want to delete the application?').then(function() {
|
return patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then(function() {
|
||||||
return resin.models.application.remove(params.name);
|
return resin.models.application.remove(params.name);
|
||||||
}).tap(function() {
|
}).tap(function() {
|
||||||
return resin.models.application.get(params.name).then(function(application) {
|
return resin.models.application.get(params.name).then(function(application) {
|
||||||
@ -117,7 +119,7 @@
|
|||||||
}).then(function() {
|
}).then(function() {
|
||||||
var message;
|
var message;
|
||||||
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 helpers.confirm(options.yes, message);
|
return patterns.confirm(options.yes, message);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return resin.models.application.get(params.name).get('git_repository').then(function(gitRepository) {
|
return resin.models.application.get(params.name).get('git_repository').then(function(gitRepository) {
|
||||||
return vcs.initialize(currentDirectory).then(function() {
|
return vcs.initialize(currentDirectory).then(function() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var Promise, _, async, capitano, commandOptions, deviceConfig, events, form, helpers, htmlToText, image, inject, manager, pine, registerDevice, resin, vcs, visuals;
|
var Promise, _, capitano, commandOptions, events, form, fs, helpers, init, patterns, resin, rimraf, stepHandler, umount, vcs, visuals;
|
||||||
|
|
||||||
Promise = require('bluebird');
|
Promise = require('bluebird');
|
||||||
|
|
||||||
@ -7,31 +7,25 @@
|
|||||||
|
|
||||||
_ = require('lodash');
|
_ = require('lodash');
|
||||||
|
|
||||||
async = require('async');
|
|
||||||
|
|
||||||
resin = require('resin-sdk');
|
resin = require('resin-sdk');
|
||||||
|
|
||||||
visuals = require('resin-cli-visuals');
|
visuals = require('resin-cli-visuals');
|
||||||
|
|
||||||
vcs = require('resin-vcs');
|
vcs = require('resin-vcs');
|
||||||
|
|
||||||
manager = require('resin-image-manager');
|
|
||||||
|
|
||||||
image = require('resin-image');
|
|
||||||
|
|
||||||
inject = require('resin-config-inject');
|
|
||||||
|
|
||||||
registerDevice = require('resin-register-device');
|
|
||||||
|
|
||||||
pine = require('resin-pine');
|
|
||||||
|
|
||||||
deviceConfig = require('resin-device-config');
|
|
||||||
|
|
||||||
form = require('resin-cli-form');
|
form = require('resin-cli-form');
|
||||||
|
|
||||||
events = require('resin-cli-events');
|
events = require('resin-cli-events');
|
||||||
|
|
||||||
htmlToText = require('html-to-text');
|
init = require('resin-device-init');
|
||||||
|
|
||||||
|
fs = Promise.promisifyAll(require('fs'));
|
||||||
|
|
||||||
|
rimraf = Promise.promisify(require('rimraf'));
|
||||||
|
|
||||||
|
umount = Promise.promisifyAll(require('umount'));
|
||||||
|
|
||||||
|
patterns = require('../utils/patterns');
|
||||||
|
|
||||||
helpers = require('../utils/helpers');
|
helpers = require('../utils/helpers');
|
||||||
|
|
||||||
@ -80,7 +74,7 @@
|
|||||||
options: [commandOptions.yes],
|
options: [commandOptions.yes],
|
||||||
permission: 'user',
|
permission: 'user',
|
||||||
action: function(params, options, done) {
|
action: function(params, options, done) {
|
||||||
return helpers.confirm(options.yes, 'Are you sure you want to delete the device?').then(function() {
|
return patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then(function() {
|
||||||
return resin.models.device.remove(params.uuid);
|
return resin.models.device.remove(params.uuid);
|
||||||
}).tap(function() {
|
}).tap(function() {
|
||||||
return events.send('device.delete', {
|
return events.send('device.delete', {
|
||||||
@ -122,155 +116,83 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
stepHandler = function(step) {
|
||||||
|
var bar;
|
||||||
|
step.on('stdout', _.bind(process.stdout.write, process.stdout));
|
||||||
|
step.on('stderr', _.bind(process.stderr.write, process.stderr));
|
||||||
|
step.on('state', function(state) {
|
||||||
|
if (state.operation.command === 'burn') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return console.log(helpers.stateToString(state));
|
||||||
|
});
|
||||||
|
bar = new visuals.Progress('Writing Device OS');
|
||||||
|
step.on('burn', _.bind(bar.update, bar));
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
step.on('error', reject);
|
||||||
|
return step.on('end', resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
exports.init = {
|
exports.init = {
|
||||||
signature: 'device init [device]',
|
signature: 'device init',
|
||||||
description: 'initialise a device with resin os',
|
description: 'initialise a device with resin os',
|
||||||
help: 'Use this command to download the OS image of a certain application and write it to an SD Card.\n\nNote that this command requires admin privileges.\n\nIf `device` is omitted, you will be prompted to select a device interactively.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nYou can quiet the progress bar and other logging information by passing the `--quiet` boolean option.\n\nYou need to configure the network type and other settings:\n\nEthernet:\n You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".\n\nWifi:\n You can setup the device OS to use wifi by setting the `--network` option to "wifi".\n If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.\n\nYou can omit network related options to be asked about them interactively.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp\n $ resin device init --application MyApp --network ethernet\n $ resin device init /dev/disk2 --application MyApp --network wifi --ssid MyNetwork --key secret',
|
help: 'Use this command to download the OS image of a certain application and write it to an SD Card.\n\nNotice this command may ask for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp',
|
||||||
options: [commandOptions.optionalApplication, commandOptions.network, commandOptions.wifiSsid, commandOptions.wifiKey],
|
options: [commandOptions.optionalApplication, commandOptions.yes],
|
||||||
permission: 'user',
|
permission: 'user',
|
||||||
root: true,
|
root: true,
|
||||||
action: function(params, options, done) {
|
action: function(params, options, done) {
|
||||||
var networkOptions;
|
return Promise["try"](function() {
|
||||||
networkOptions = {
|
|
||||||
network: options.network,
|
|
||||||
wifiSsid: options.ssid,
|
|
||||||
wifiKey: options.key
|
|
||||||
};
|
|
||||||
return async.waterfall([
|
|
||||||
function(callback) {
|
|
||||||
if (options.application != null) {
|
if (options.application != null) {
|
||||||
return callback(null, options.application);
|
return options.application;
|
||||||
}
|
}
|
||||||
return vcs.getApplicationName(process.cwd()).nodeify(callback);
|
return vcs.getApplicationName(process.cwd());
|
||||||
}, function(applicationName, callback) {
|
}).then(resin.models.application.get).then(function(application) {
|
||||||
options.application = applicationName;
|
console.info('Getting configuration options');
|
||||||
return resin.models.application.has(options.application).nodeify(callback);
|
return patterns.askDeviceOptions(application.device_type).tap(function(answers) {
|
||||||
}, function(hasApplication, callback) {
|
|
||||||
if (!hasApplication) {
|
|
||||||
return callback(new Error("Invalid application: " + options.application));
|
|
||||||
}
|
|
||||||
if (params.device != null) {
|
|
||||||
return callback(null, params.device);
|
|
||||||
}
|
|
||||||
return form.ask({
|
|
||||||
type: 'drive',
|
|
||||||
message: 'Select a drive'
|
|
||||||
}).nodeify(callback);
|
|
||||||
}, function(device, callback) {
|
|
||||||
var message;
|
var message;
|
||||||
params.device = device;
|
if (answers.drive != null) {
|
||||||
message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
|
message = "This will erase " + answers.drive + ". Are you sure?";
|
||||||
if (options.yes) {
|
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umount.umountAsync);
|
||||||
return callback(null, true);
|
|
||||||
} else {
|
|
||||||
return form.ask({
|
|
||||||
message: message,
|
|
||||||
type: 'confirm',
|
|
||||||
"default": false
|
|
||||||
}).nodeify(callback);
|
|
||||||
}
|
}
|
||||||
}, function(confirmed, callback) {
|
}).then(function(answers) {
|
||||||
if (!confirmed) {
|
console.info('Getting device operating system');
|
||||||
return done();
|
return patterns.download(application.device_type).then(function(temporalPath) {
|
||||||
|
var uuid;
|
||||||
|
uuid = resin.models.device.generateUUID();
|
||||||
|
console.log("Registering to " + application.app_name + ": " + uuid);
|
||||||
|
return resin.models.device.register(application.app_name, uuid).tap(function(device) {
|
||||||
|
console.log('Configuring operating system');
|
||||||
|
return init.configure(temporalPath, device.uuid, answers).then(stepHandler).then(function() {
|
||||||
|
console.log('Initializing device');
|
||||||
|
return init.initialize(temporalPath, device.uuid, answers).then(stepHandler);
|
||||||
|
}).tap(function() {
|
||||||
|
if (answers.drive == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (networkOptions.network != null) {
|
return umount.umountAsync(answers.drive).tap(function() {
|
||||||
return callback();
|
return console.log("You can safely remove " + answers.drive + " now");
|
||||||
}
|
|
||||||
return form.run([
|
|
||||||
{
|
|
||||||
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(function(parameters) {
|
|
||||||
return _.extend(networkOptions, parameters);
|
|
||||||
}).nodeify(callback);
|
|
||||||
}, function(callback) {
|
|
||||||
console.info("Checking application: " + options.application);
|
|
||||||
return resin.models.application.get(options.application).nodeify(callback);
|
|
||||||
}, function(application, callback) {
|
|
||||||
return async.parallel({
|
|
||||||
manifest: function(callback) {
|
|
||||||
console.info('Getting device manifest for the application');
|
|
||||||
return resin.models.device.getManifestBySlug(application.device_type).nodeify(callback);
|
|
||||||
},
|
|
||||||
config: function(callback) {
|
|
||||||
console.info('Fetching application configuration');
|
|
||||||
return deviceConfig.get(options.application, networkOptions).nodeify(callback);
|
|
||||||
}
|
|
||||||
}, callback);
|
|
||||||
}, function(results, callback) {
|
|
||||||
params.manifest = results.manifest;
|
|
||||||
console.info('Associating the device');
|
|
||||||
return registerDevice.register(pine, results.config, function(error, device) {
|
|
||||||
if (error != null) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
results.config.deviceId = device.id;
|
|
||||||
results.config.uuid = device.uuid;
|
|
||||||
results.config.registered_at = Math.floor(Date.now() / 1000);
|
|
||||||
params.uuid = results.config.uuid;
|
|
||||||
return callback(null, results);
|
|
||||||
});
|
});
|
||||||
}, function(results, callback) {
|
|
||||||
var bar, spinner;
|
|
||||||
console.info('Initializing device operating system image');
|
|
||||||
console.info('This may take a few minutes');
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.log(results.config);
|
|
||||||
}
|
|
||||||
bar = new visuals.Progress('Downloading Device OS');
|
|
||||||
spinner = new visuals.Spinner('Downloading Device OS (size unknown)');
|
|
||||||
return manager.configure(params.manifest, results.config, function(error, imagePath, removeCallback) {
|
|
||||||
spinner.stop();
|
|
||||||
return callback(error, imagePath, removeCallback);
|
|
||||||
}, function(state) {
|
|
||||||
if (state != null) {
|
|
||||||
return bar.update(state);
|
|
||||||
} else {
|
|
||||||
return spinner.start();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, function(configuredImagePath, removeCallback, callback) {
|
}).then(function(device) {
|
||||||
var bar;
|
console.log('Done');
|
||||||
console.info('The base image was cached to improve initialization time of similar devices');
|
return device.uuid;
|
||||||
console.info('Attempting to write operating system image to drive');
|
})["finally"](function() {
|
||||||
bar = new visuals.Progress('Writing Device OS');
|
return fs.statAsync(temporalPath).then(function(stat) {
|
||||||
return image.write({
|
if (stat.isDirectory()) {
|
||||||
device: params.device,
|
return rimraf(temporalPath);
|
||||||
image: configuredImagePath,
|
|
||||||
progress: _.bind(bar.update, bar)
|
|
||||||
}, function(error) {
|
|
||||||
if (error != null) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
}
|
||||||
return callback(null, configuredImagePath, removeCallback);
|
return fs.unlinkAsync(temporalPath);
|
||||||
|
})["catch"](function(error) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
}, function(temporalImagePath, removeCallback, callback) {
|
});
|
||||||
console.info('Image written successfully');
|
});
|
||||||
return removeCallback(callback);
|
});
|
||||||
}, function(callback) {
|
}).nodeify(done);
|
||||||
return resin.models.device.get(params.uuid).nodeify(callback);
|
|
||||||
}, function(device, callback) {
|
|
||||||
console.info("Device created: " + device.name);
|
|
||||||
return callback(null, params.uuid);
|
|
||||||
}
|
|
||||||
], done);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var Promise, _, commandOptions, events, helpers, resin, visuals;
|
var Promise, _, commandOptions, events, patterns, resin, visuals;
|
||||||
|
|
||||||
Promise = require('bluebird');
|
Promise = require('bluebird');
|
||||||
|
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
commandOptions = require('./command-options');
|
commandOptions = require('./command-options');
|
||||||
|
|
||||||
helpers = require('../utils/helpers');
|
patterns = require('../utils/patterns');
|
||||||
|
|
||||||
exports.list = {
|
exports.list = {
|
||||||
signature: 'envs',
|
signature: 'envs',
|
||||||
@ -58,7 +58,7 @@
|
|||||||
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 helpers.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then(function() {
|
return patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then(function() {
|
||||||
if (options.device) {
|
if (options.device) {
|
||||||
resin.models.environmentVariables.device.remove(params.id);
|
resin.models.environmentVariables.device.remove(params.id);
|
||||||
return events.send('deviceEnvironmentVariable.delete', {
|
return events.send('deviceEnvironmentVariable.delete', {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var Promise, _, capitano, commandOptions, events, fs, helpers, resin, visuals;
|
var Promise, _, capitano, commandOptions, events, fs, patterns, resin, visuals;
|
||||||
|
|
||||||
Promise = require('bluebird');
|
Promise = require('bluebird');
|
||||||
|
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
commandOptions = require('./command-options');
|
commandOptions = require('./command-options');
|
||||||
|
|
||||||
helpers = require('../utils/helpers');
|
patterns = require('../utils/patterns');
|
||||||
|
|
||||||
exports.list = {
|
exports.list = {
|
||||||
signature: 'keys',
|
signature: 'keys',
|
||||||
@ -51,7 +51,7 @@
|
|||||||
options: [commandOptions.yes],
|
options: [commandOptions.yes],
|
||||||
permission: 'user',
|
permission: 'user',
|
||||||
action: function(params, options, done) {
|
action: function(params, options, done) {
|
||||||
return helpers.confirm(options.yes, 'Are you sure you want to delete the key?').then(function() {
|
return patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then(function() {
|
||||||
return resin.models.key.remove(params.id);
|
return resin.models.key.remove(params.id);
|
||||||
}).tap(function() {
|
}).tap(function() {
|
||||||
return events.send('publicKey.delete', {
|
return events.send('publicKey.delete', {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var Promise, capitano, form, helpers, mkdirp, resin;
|
var Promise, capitano, form, mkdirp, patterns, resin;
|
||||||
|
|
||||||
Promise = require('bluebird');
|
Promise = require('bluebird');
|
||||||
|
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
form = require('resin-cli-form');
|
form = require('resin-cli-form');
|
||||||
|
|
||||||
helpers = require('../utils/helpers');
|
patterns = require('../utils/patterns');
|
||||||
|
|
||||||
exports.wizard = {
|
exports.wizard = {
|
||||||
signature: 'quickstart [name]',
|
signature: 'quickstart [name]',
|
||||||
@ -24,18 +24,18 @@
|
|||||||
if (params.name != null) {
|
if (params.name != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return helpers.selectApplication().tap(function(applicationName) {
|
return patterns.selectApplication().tap(function(applicationName) {
|
||||||
return capitano.runAsync("app create " + applicationName);
|
return capitano.runAsync("app create " + applicationName);
|
||||||
}).then(function(applicationName) {
|
}).then(function(applicationName) {
|
||||||
return params.name = applicationName;
|
return params.name = applicationName;
|
||||||
});
|
});
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return capitano.runAsync("device init --application " + params.name);
|
return capitano.runAsync("device init --application " + params.name);
|
||||||
}).tap(helpers.awaitDevice).then(function(uuid) {
|
}).tap(patterns.awaitDevice).then(function(uuid) {
|
||||||
return capitano.runAsync("device " + uuid);
|
return capitano.runAsync("device " + uuid);
|
||||||
}).tap(function() {
|
}).tap(function() {
|
||||||
return console.log('Your device is ready, lets start pushing some code!');
|
return console.log('Your device is ready, lets start pushing some code!');
|
||||||
}).then(helpers.selectProjectDirectory).tap(mkdirp).tap(process.chdir).then(function() {
|
}).then(patterns.selectProjectDirectory).tap(mkdirp).tap(process.chdir).then(function() {
|
||||||
return capitano.runAsync("app associate " + params.name);
|
return capitano.runAsync("app associate " + params.name);
|
||||||
}).then(function(remoteUrl) {
|
}).then(function(remoteUrl) {
|
||||||
console.log("Resin git remote added: " + remoteUrl);
|
console.log("Resin git remote added: " + remoteUrl);
|
||||||
|
@ -1,97 +1,37 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var Promise, _, form, resin, visuals;
|
var _, chalk, os;
|
||||||
|
|
||||||
_ = require('lodash');
|
_ = require('lodash');
|
||||||
|
|
||||||
Promise = require('bluebird');
|
_.str = require('underscore.string');
|
||||||
|
|
||||||
form = require('resin-cli-form');
|
os = require('os');
|
||||||
|
|
||||||
visuals = require('resin-cli-visuals');
|
chalk = require('chalk');
|
||||||
|
|
||||||
resin = require('resin-sdk');
|
exports.getOperatingSystem = function() {
|
||||||
|
var platform;
|
||||||
exports.selectDeviceType = function() {
|
platform = os.platform();
|
||||||
return form.ask({
|
if (platform === 'darwin') {
|
||||||
message: 'Device Type',
|
platform = 'osx';
|
||||||
type: 'list',
|
}
|
||||||
choices: ['Raspberry Pi', 'Raspberry Pi 2', 'BeagleBone Black']
|
return platform;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.confirm = function(yesOption, message) {
|
exports.stateToString = function(state) {
|
||||||
return Promise["try"](function() {
|
var percentage, result;
|
||||||
if (yesOption) {
|
percentage = _.str.lpad(state.percentage, 3, '0') + '%';
|
||||||
return true;
|
result = (chalk.blue(percentage)) + " " + (chalk.cyan(state.operation.command));
|
||||||
|
switch (state.operation.command) {
|
||||||
|
case 'copy':
|
||||||
|
return result + " " + state.operation.from.path + " -> " + state.operation.to.path;
|
||||||
|
case 'replace':
|
||||||
|
return result + " " + state.operation.file.path + ", " + state.operation.copy + " -> " + state.operation.replace;
|
||||||
|
case 'run-script':
|
||||||
|
return result + " " + state.operation.script;
|
||||||
|
default:
|
||||||
|
throw new Error("Unsupported operation: " + state.operation.type);
|
||||||
}
|
}
|
||||||
return form.ask({
|
|
||||||
message: message,
|
|
||||||
type: 'confirm',
|
|
||||||
"default": false
|
|
||||||
});
|
|
||||||
}).then(function(confirmed) {
|
|
||||||
if (!confirmed) {
|
|
||||||
throw new Error('Aborted');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.selectApplication = function() {
|
|
||||||
return resin.models.application.hasAny().then(function(hasAnyApplications) {
|
|
||||||
if (!hasAnyApplications) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return resin.models.application.getAll().then(function(applications) {
|
|
||||||
applications = _.pluck(applications, 'app_name');
|
|
||||||
applications.unshift({
|
|
||||||
name: 'Create a new application',
|
|
||||||
value: null
|
|
||||||
});
|
|
||||||
return form.ask({
|
|
||||||
message: 'Select an application',
|
|
||||||
type: 'list',
|
|
||||||
choices: applications
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).then(function(application) {
|
|
||||||
if (application != null) {
|
|
||||||
return application;
|
|
||||||
}
|
|
||||||
return form.ask({
|
|
||||||
message: 'Choose a Name for your new application',
|
|
||||||
type: 'input'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.selectProjectDirectory = function() {
|
|
||||||
return resin.settings.get('projectsDirectory').then(function(projectsDirectory) {
|
|
||||||
return form.ask({
|
|
||||||
message: 'Please choose a directory for your code',
|
|
||||||
type: 'input',
|
|
||||||
"default": projectsDirectory
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.awaitDevice = function(uuid) {
|
|
||||||
var poll, spinner;
|
|
||||||
spinner = new visuals.Spinner("Awaiting device: " + uuid);
|
|
||||||
poll = function() {
|
|
||||||
return resin.models.device.isOnline(uuid).then(function(isOnline) {
|
|
||||||
if (isOnline) {
|
|
||||||
spinner.stop();
|
|
||||||
console.info("Device became online: " + uuid);
|
|
||||||
} else {
|
|
||||||
spinner.start();
|
|
||||||
return Promise.delay(3000).then(poll);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return resin.models.device.getName(uuid).then(function(deviceName) {
|
|
||||||
console.info("Waiting for " + deviceName + " to connect to resin...");
|
|
||||||
return poll()["return"](uuid);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
131
build/utils/patterns.js
Normal file
131
build/utils/patterns.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
(function() {
|
||||||
|
var Promise, _, form, helpers, manager, resin, visuals;
|
||||||
|
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
Promise = require('bluebird');
|
||||||
|
|
||||||
|
form = require('resin-cli-form');
|
||||||
|
|
||||||
|
visuals = require('resin-cli-visuals');
|
||||||
|
|
||||||
|
resin = require('resin-sdk');
|
||||||
|
|
||||||
|
manager = require('resin-image-manager');
|
||||||
|
|
||||||
|
helpers = require('./helpers');
|
||||||
|
|
||||||
|
exports.selectDeviceType = function() {
|
||||||
|
return resin.models.device.getSupportedDeviceTypes().then(function(deviceTypes) {
|
||||||
|
return form.ask({
|
||||||
|
message: 'Device Type',
|
||||||
|
type: 'list',
|
||||||
|
choices: deviceTypes
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.confirm = function(yesOption, message) {
|
||||||
|
return Promise["try"](function() {
|
||||||
|
if (yesOption) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return form.ask({
|
||||||
|
message: message,
|
||||||
|
type: 'confirm',
|
||||||
|
"default": false
|
||||||
|
});
|
||||||
|
}).then(function(confirmed) {
|
||||||
|
if (!confirmed) {
|
||||||
|
throw new Error('Aborted');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.selectApplication = function() {
|
||||||
|
return resin.models.application.hasAny().then(function(hasAnyApplications) {
|
||||||
|
if (!hasAnyApplications) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return resin.models.application.getAll().then(function(applications) {
|
||||||
|
applications = _.pluck(applications, 'app_name');
|
||||||
|
applications.unshift({
|
||||||
|
name: 'Create a new application',
|
||||||
|
value: null
|
||||||
|
});
|
||||||
|
return form.ask({
|
||||||
|
message: 'Select an application',
|
||||||
|
type: 'list',
|
||||||
|
choices: applications
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function(application) {
|
||||||
|
if (application != null) {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
return form.ask({
|
||||||
|
message: 'Choose a Name for your new application',
|
||||||
|
type: 'input'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.selectProjectDirectory = function() {
|
||||||
|
return resin.settings.get('projectsDirectory').then(function(projectsDirectory) {
|
||||||
|
return form.ask({
|
||||||
|
message: 'Please choose a directory for your code',
|
||||||
|
type: 'input',
|
||||||
|
"default": projectsDirectory
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.awaitDevice = function(uuid) {
|
||||||
|
var poll, spinner;
|
||||||
|
spinner = new visuals.Spinner("Awaiting device: " + uuid);
|
||||||
|
poll = function() {
|
||||||
|
return resin.models.device.isOnline(uuid).then(function(isOnline) {
|
||||||
|
if (isOnline) {
|
||||||
|
spinner.stop();
|
||||||
|
console.info("Device became online: " + uuid);
|
||||||
|
} else {
|
||||||
|
spinner.start();
|
||||||
|
return Promise.delay(3000).then(poll);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return resin.models.device.getName(uuid).then(function(deviceName) {
|
||||||
|
console.info("Waiting for " + deviceName + " to connect to resin...");
|
||||||
|
return poll()["return"](uuid);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.askDeviceOptions = function(deviceType) {
|
||||||
|
return resin.models.config.getDeviceOptions(deviceType).then(form.run).then(function(answers) {
|
||||||
|
if (answers.os == null) {
|
||||||
|
answers.os = helpers.getOperatingSystem();
|
||||||
|
}
|
||||||
|
return answers;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.download = function(deviceType) {
|
||||||
|
return manager.get(deviceType).then(function(stream) {
|
||||||
|
var bar, spinner;
|
||||||
|
bar = new visuals.Progress('Downloading Device OS');
|
||||||
|
spinner = new visuals.Spinner('Downloading Device OS (size unknown)');
|
||||||
|
stream.on('progress', function(state) {
|
||||||
|
if (state != null) {
|
||||||
|
return bar.update(state);
|
||||||
|
} else {
|
||||||
|
return spinner.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stream.on('end', function() {
|
||||||
|
return spinner.stop();
|
||||||
|
});
|
||||||
|
return manager.pipeTemporal(stream);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}).call(this);
|
@ -5,6 +5,7 @@ commandOptions = require('./command-options')
|
|||||||
vcs = require('resin-vcs')
|
vcs = require('resin-vcs')
|
||||||
events = require('resin-cli-events')
|
events = require('resin-cli-events')
|
||||||
helpers = require('../utils/helpers')
|
helpers = require('../utils/helpers')
|
||||||
|
patterns = require('../utils/patterns')
|
||||||
|
|
||||||
exports.create =
|
exports.create =
|
||||||
signature: 'app create <name>'
|
signature: 'app create <name>'
|
||||||
@ -42,7 +43,7 @@ exports.create =
|
|||||||
if hasApplication
|
if hasApplication
|
||||||
throw new Error('You already have an application with that name!')
|
throw new Error('You already have an application with that name!')
|
||||||
|
|
||||||
.then(helpers.selectDeviceType).then (deviceType) ->
|
.then(patterns.selectDeviceType).then (deviceType) ->
|
||||||
return resin.models.application.create(params.name, deviceType)
|
return resin.models.application.create(params.name, deviceType)
|
||||||
.then (application) ->
|
.then (application) ->
|
||||||
console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
|
console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
|
||||||
@ -128,7 +129,7 @@ exports.remove =
|
|||||||
options: [ commandOptions.yes ]
|
options: [ commandOptions.yes ]
|
||||||
permission: 'user'
|
permission: 'user'
|
||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
helpers.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
|
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
|
||||||
resin.models.application.remove(params.name)
|
resin.models.application.remove(params.name)
|
||||||
.tap ->
|
.tap ->
|
||||||
resin.models.application.get(params.name).then (application) ->
|
resin.models.application.get(params.name).then (application) ->
|
||||||
@ -162,7 +163,7 @@ exports.associate =
|
|||||||
|
|
||||||
.then ->
|
.then ->
|
||||||
message = "Are you sure you want to associate #{currentDirectory} with #{params.name}?"
|
message = "Are you sure you want to associate #{currentDirectory} with #{params.name}?"
|
||||||
helpers.confirm(options.yes, message)
|
patterns.confirm(options.yes, message)
|
||||||
.then ->
|
.then ->
|
||||||
|
|
||||||
resin.models.application.get(params.name).get('git_repository').then (gitRepository) ->
|
resin.models.application.get(params.name).get('git_repository').then (gitRepository) ->
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
Promise = require('bluebird')
|
Promise = require('bluebird')
|
||||||
capitano = require('capitano')
|
capitano = require('capitano')
|
||||||
_ = require('lodash')
|
_ = require('lodash')
|
||||||
async = require('async')
|
|
||||||
resin = require('resin-sdk')
|
resin = require('resin-sdk')
|
||||||
visuals = require('resin-cli-visuals')
|
visuals = require('resin-cli-visuals')
|
||||||
vcs = require('resin-vcs')
|
vcs = require('resin-vcs')
|
||||||
manager = require('resin-image-manager')
|
|
||||||
image = require('resin-image')
|
|
||||||
inject = require('resin-config-inject')
|
|
||||||
registerDevice = require('resin-register-device')
|
|
||||||
pine = require('resin-pine')
|
|
||||||
deviceConfig = require('resin-device-config')
|
|
||||||
form = require('resin-cli-form')
|
form = require('resin-cli-form')
|
||||||
events = require('resin-cli-events')
|
events = require('resin-cli-events')
|
||||||
htmlToText = require('html-to-text')
|
init = require('resin-device-init')
|
||||||
|
fs = Promise.promisifyAll(require('fs'))
|
||||||
|
rimraf = Promise.promisify(require('rimraf'))
|
||||||
|
umount = Promise.promisifyAll(require('umount'))
|
||||||
|
patterns = require('../utils/patterns')
|
||||||
helpers = require('../utils/helpers')
|
helpers = require('../utils/helpers')
|
||||||
|
|
||||||
commandOptions = require('./command-options')
|
commandOptions = require('./command-options')
|
||||||
@ -107,7 +104,7 @@ exports.remove =
|
|||||||
options: [ commandOptions.yes ]
|
options: [ commandOptions.yes ]
|
||||||
permission: 'user'
|
permission: 'user'
|
||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
helpers.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
|
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
|
||||||
resin.models.device.remove(params.uuid)
|
resin.models.device.remove(params.uuid)
|
||||||
.tap ->
|
.tap ->
|
||||||
events.send('device.delete', device: params.uuid)
|
events.send('device.delete', device: params.uuid)
|
||||||
@ -156,186 +153,84 @@ exports.rename =
|
|||||||
events.send('device.rename', device: params.uuid)
|
events.send('device.rename', device: params.uuid)
|
||||||
.nodeify(done)
|
.nodeify(done)
|
||||||
|
|
||||||
|
stepHandler = (step) ->
|
||||||
|
|
||||||
|
step.on('stdout', _.bind(process.stdout.write, process.stdout))
|
||||||
|
step.on('stderr', _.bind(process.stderr.write, process.stderr))
|
||||||
|
|
||||||
|
step.on 'state', (state) ->
|
||||||
|
return if state.operation.command is 'burn'
|
||||||
|
console.log(helpers.stateToString(state))
|
||||||
|
|
||||||
|
bar = new visuals.Progress('Writing Device OS')
|
||||||
|
|
||||||
|
step.on('burn', _.bind(bar.update, bar))
|
||||||
|
|
||||||
|
return new Promise (resolve, reject) ->
|
||||||
|
step.on('error', reject)
|
||||||
|
step.on('end', resolve)
|
||||||
|
|
||||||
exports.init =
|
exports.init =
|
||||||
signature: 'device init [device]'
|
signature: 'device init'
|
||||||
description: 'initialise a device with resin os'
|
description: 'initialise a device with resin os'
|
||||||
help: '''
|
help: '''
|
||||||
Use this command to download the OS image of a certain application and write it to an SD Card.
|
Use this command to download the OS image of a certain application and write it to an SD Card.
|
||||||
|
|
||||||
Note that this command requires admin privileges.
|
Notice this command may ask for confirmation interactively.
|
||||||
|
|
||||||
If `device` is omitted, you will be prompted to select a device interactively.
|
|
||||||
|
|
||||||
Notice this command asks for confirmation interactively.
|
|
||||||
You can avoid this by passing the `--yes` boolean option.
|
You can avoid this by passing the `--yes` boolean option.
|
||||||
|
|
||||||
You can quiet the progress bar and other logging information by passing the `--quiet` boolean option.
|
|
||||||
|
|
||||||
You need to configure the network type and other settings:
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
You can omit network related options to be asked about them interactively.
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
$ resin device init
|
$ resin device init
|
||||||
$ resin device init --application MyApp
|
$ resin device init --application MyApp
|
||||||
$ resin device init --application MyApp --network ethernet
|
|
||||||
$ resin device init /dev/disk2 --application MyApp --network wifi --ssid MyNetwork --key secret
|
|
||||||
'''
|
'''
|
||||||
options: [
|
options: [
|
||||||
commandOptions.optionalApplication
|
commandOptions.optionalApplication
|
||||||
commandOptions.network
|
commandOptions.yes
|
||||||
commandOptions.wifiSsid
|
|
||||||
commandOptions.wifiKey
|
|
||||||
]
|
]
|
||||||
permission: 'user'
|
permission: 'user'
|
||||||
root: true
|
root: true
|
||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
|
Promise.try ->
|
||||||
|
return options.application if options.application?
|
||||||
|
return vcs.getApplicationName(process.cwd())
|
||||||
|
.then(resin.models.application.get)
|
||||||
|
.then (application) ->
|
||||||
|
|
||||||
networkOptions =
|
console.info('Getting configuration options')
|
||||||
network: options.network
|
patterns.askDeviceOptions(application.device_type).tap (answers) ->
|
||||||
wifiSsid: options.ssid
|
if answers.drive?
|
||||||
wifiKey: options.key
|
message = "This will erase #{answers.drive}. Are you sure?"
|
||||||
|
patterns.confirm(options.yes, message)
|
||||||
|
.return(answers.drive)
|
||||||
|
.then(umount.umountAsync)
|
||||||
|
.then (answers) ->
|
||||||
|
console.info('Getting device operating system')
|
||||||
|
patterns.download(application.device_type).then (temporalPath) ->
|
||||||
|
uuid = resin.models.device.generateUUID()
|
||||||
|
console.log("Registering to #{application.app_name}: #{uuid}")
|
||||||
|
resin.models.device.register(application.app_name, uuid).tap (device) ->
|
||||||
|
console.log('Configuring operating system')
|
||||||
|
init.configure(temporalPath, device.uuid, answers).then(stepHandler).then ->
|
||||||
|
console.log('Initializing device')
|
||||||
|
init.initialize(temporalPath, device.uuid, answers).then(stepHandler)
|
||||||
|
.tap ->
|
||||||
|
return if not answers.drive?
|
||||||
|
umount.umountAsync(answers.drive).tap ->
|
||||||
|
console.log("You can safely remove #{answers.drive} now")
|
||||||
|
.then (device) ->
|
||||||
|
console.log('Done')
|
||||||
|
return device.uuid
|
||||||
|
|
||||||
async.waterfall [
|
.finally ->
|
||||||
|
fs.statAsync(temporalPath).then (stat) ->
|
||||||
|
return rimraf(temporalPath) if stat.isDirectory()
|
||||||
|
return fs.unlinkAsync(temporalPath)
|
||||||
|
.catch (error) ->
|
||||||
|
|
||||||
(callback) ->
|
# Ignore errors if temporary file does not exist
|
||||||
return callback(null, options.application) if options.application?
|
return if error.code is 'ENOENT'
|
||||||
vcs.getApplicationName(process.cwd()).nodeify(callback)
|
|
||||||
|
|
||||||
(applicationName, callback) ->
|
throw error
|
||||||
options.application = applicationName
|
|
||||||
resin.models.application.has(options.application).nodeify(callback)
|
|
||||||
|
|
||||||
(hasApplication, callback) ->
|
.nodeify(done)
|
||||||
if not hasApplication
|
|
||||||
return callback(new Error("Invalid application: #{options.application}"))
|
|
||||||
|
|
||||||
return callback(null, params.device) if params.device?
|
|
||||||
form.ask
|
|
||||||
type: 'drive'
|
|
||||||
message: 'Select a drive'
|
|
||||||
.nodeify(callback)
|
|
||||||
|
|
||||||
(device, callback) ->
|
|
||||||
params.device = device
|
|
||||||
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
|
|
||||||
if options.yes
|
|
||||||
return callback(null, true)
|
|
||||||
else
|
|
||||||
form.ask
|
|
||||||
message: message
|
|
||||||
type: 'confirm'
|
|
||||||
default: false
|
|
||||||
.nodeify(callback)
|
|
||||||
|
|
||||||
(confirmed, callback) ->
|
|
||||||
return done() if not confirmed
|
|
||||||
return callback() if networkOptions.network?
|
|
||||||
form.run [
|
|
||||||
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)
|
|
||||||
.nodeify(callback)
|
|
||||||
|
|
||||||
(callback) ->
|
|
||||||
console.info("Checking application: #{options.application}")
|
|
||||||
resin.models.application.get(options.application).nodeify(callback)
|
|
||||||
|
|
||||||
(application, callback) ->
|
|
||||||
async.parallel
|
|
||||||
|
|
||||||
manifest: (callback) ->
|
|
||||||
console.info('Getting device manifest for the application')
|
|
||||||
resin.models.device.getManifestBySlug(application.device_type).nodeify(callback)
|
|
||||||
|
|
||||||
config: (callback) ->
|
|
||||||
console.info('Fetching application configuration')
|
|
||||||
deviceConfig.get(options.application, networkOptions).nodeify(callback)
|
|
||||||
|
|
||||||
, callback
|
|
||||||
|
|
||||||
(results, callback) ->
|
|
||||||
params.manifest = results.manifest
|
|
||||||
console.info('Associating the device')
|
|
||||||
|
|
||||||
registerDevice.register pine, results.config, (error, device) ->
|
|
||||||
return callback(error) if error?
|
|
||||||
|
|
||||||
# Associate a device
|
|
||||||
results.config.deviceId = device.id
|
|
||||||
results.config.uuid = device.uuid
|
|
||||||
results.config.registered_at = Math.floor(Date.now() / 1000)
|
|
||||||
|
|
||||||
params.uuid = results.config.uuid
|
|
||||||
|
|
||||||
return callback(null, results)
|
|
||||||
|
|
||||||
(results, callback) ->
|
|
||||||
console.info('Initializing device operating system image')
|
|
||||||
console.info('This may take a few minutes')
|
|
||||||
|
|
||||||
if process.env.DEBUG
|
|
||||||
console.log(results.config)
|
|
||||||
|
|
||||||
bar = new visuals.Progress('Downloading Device OS')
|
|
||||||
spinner = new visuals.Spinner('Downloading Device OS (size unknown)')
|
|
||||||
|
|
||||||
manager.configure params.manifest, results.config, (error, imagePath, removeCallback) ->
|
|
||||||
spinner.stop()
|
|
||||||
return callback(error, imagePath, removeCallback)
|
|
||||||
, (state) ->
|
|
||||||
if state?
|
|
||||||
bar.update(state)
|
|
||||||
else
|
|
||||||
spinner.start()
|
|
||||||
|
|
||||||
(configuredImagePath, removeCallback, callback) ->
|
|
||||||
console.info('The base image was cached to improve initialization time of similar devices')
|
|
||||||
|
|
||||||
console.info('Attempting to write operating system image to drive')
|
|
||||||
|
|
||||||
bar = new visuals.Progress('Writing Device OS')
|
|
||||||
image.write
|
|
||||||
device: params.device
|
|
||||||
image: configuredImagePath
|
|
||||||
progress: _.bind(bar.update, bar)
|
|
||||||
, (error) ->
|
|
||||||
return callback(error) if error?
|
|
||||||
return callback(null, configuredImagePath, removeCallback)
|
|
||||||
|
|
||||||
(temporalImagePath, removeCallback, callback) ->
|
|
||||||
console.info('Image written successfully')
|
|
||||||
removeCallback(callback)
|
|
||||||
|
|
||||||
(callback) ->
|
|
||||||
resin.models.device.get(params.uuid).nodeify(callback)
|
|
||||||
|
|
||||||
(device, callback) ->
|
|
||||||
console.info("Device created: #{device.name}")
|
|
||||||
return callback(null, params.uuid)
|
|
||||||
|
|
||||||
], done
|
|
||||||
|
@ -4,7 +4,7 @@ resin = require('resin-sdk')
|
|||||||
visuals = require('resin-cli-visuals')
|
visuals = require('resin-cli-visuals')
|
||||||
events = require('resin-cli-events')
|
events = require('resin-cli-events')
|
||||||
commandOptions = require('./command-options')
|
commandOptions = require('./command-options')
|
||||||
helpers = require('../utils/helpers')
|
patterns = require('../utils/patterns')
|
||||||
|
|
||||||
exports.list =
|
exports.list =
|
||||||
signature: 'envs'
|
signature: 'envs'
|
||||||
@ -83,7 +83,7 @@ exports.remove =
|
|||||||
]
|
]
|
||||||
permission: 'user'
|
permission: 'user'
|
||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
helpers.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
|
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
|
||||||
if options.device
|
if options.device
|
||||||
resin.models.environmentVariables.device.remove(params.id)
|
resin.models.environmentVariables.device.remove(params.id)
|
||||||
events.send('deviceEnvironmentVariable.delete', id: params.id)
|
events.send('deviceEnvironmentVariable.delete', id: params.id)
|
||||||
|
@ -6,7 +6,7 @@ capitano = require('capitano')
|
|||||||
visuals = require('resin-cli-visuals')
|
visuals = require('resin-cli-visuals')
|
||||||
events = require('resin-cli-events')
|
events = require('resin-cli-events')
|
||||||
commandOptions = require('./command-options')
|
commandOptions = require('./command-options')
|
||||||
helpers = require('../utils/helpers')
|
patterns = require('../utils/patterns')
|
||||||
|
|
||||||
exports.list =
|
exports.list =
|
||||||
signature: 'keys'
|
signature: 'keys'
|
||||||
@ -68,7 +68,7 @@ exports.remove =
|
|||||||
options: [ commandOptions.yes ]
|
options: [ commandOptions.yes ]
|
||||||
permission: 'user'
|
permission: 'user'
|
||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
helpers.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
|
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
|
||||||
resin.models.key.remove(params.id)
|
resin.models.key.remove(params.id)
|
||||||
.tap ->
|
.tap ->
|
||||||
events.send('publicKey.delete', id: params.id)
|
events.send('publicKey.delete', id: params.id)
|
||||||
|
@ -3,7 +3,7 @@ capitano = Promise.promisifyAll(require('capitano'))
|
|||||||
mkdirp = Promise.promisify(require('mkdirp'))
|
mkdirp = Promise.promisify(require('mkdirp'))
|
||||||
resin = require('resin-sdk')
|
resin = require('resin-sdk')
|
||||||
form = require('resin-cli-form')
|
form = require('resin-cli-form')
|
||||||
helpers = require('../utils/helpers')
|
patterns = require('../utils/patterns')
|
||||||
|
|
||||||
exports.wizard =
|
exports.wizard =
|
||||||
signature: 'quickstart [name]'
|
signature: 'quickstart [name]'
|
||||||
@ -28,18 +28,18 @@ exports.wizard =
|
|||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
Promise.try ->
|
Promise.try ->
|
||||||
return if params.name?
|
return if params.name?
|
||||||
helpers.selectApplication().tap (applicationName) ->
|
patterns.selectApplication().tap (applicationName) ->
|
||||||
capitano.runAsync("app create #{applicationName}")
|
capitano.runAsync("app create #{applicationName}")
|
||||||
.then (applicationName) ->
|
.then (applicationName) ->
|
||||||
params.name = applicationName
|
params.name = applicationName
|
||||||
.then ->
|
.then ->
|
||||||
return capitano.runAsync("device init --application #{params.name}")
|
return capitano.runAsync("device init --application #{params.name}")
|
||||||
.tap(helpers.awaitDevice)
|
.tap(patterns.awaitDevice)
|
||||||
.then (uuid) ->
|
.then (uuid) ->
|
||||||
return capitano.runAsync("device #{uuid}")
|
return capitano.runAsync("device #{uuid}")
|
||||||
.tap ->
|
.tap ->
|
||||||
console.log('Your device is ready, lets start pushing some code!')
|
console.log('Your device is ready, lets start pushing some code!')
|
||||||
.then(helpers.selectProjectDirectory)
|
.then(patterns.selectProjectDirectory)
|
||||||
.tap(mkdirp)
|
.tap(mkdirp)
|
||||||
.tap(process.chdir)
|
.tap(process.chdir)
|
||||||
.then ->
|
.then ->
|
||||||
|
@ -1,76 +1,23 @@
|
|||||||
_ = require('lodash')
|
_ = require('lodash')
|
||||||
Promise = require('bluebird')
|
_.str = require('underscore.string')
|
||||||
form = require('resin-cli-form')
|
os = require('os')
|
||||||
visuals = require('resin-cli-visuals')
|
chalk = require('chalk')
|
||||||
resin = require('resin-sdk')
|
|
||||||
|
|
||||||
exports.selectDeviceType = ->
|
exports.getOperatingSystem = ->
|
||||||
return form.ask
|
platform = os.platform()
|
||||||
message: 'Device Type'
|
platform = 'osx' if platform is 'darwin'
|
||||||
type: 'list'
|
return platform
|
||||||
choices: [
|
|
||||||
|
|
||||||
# Lock to specific devices until we support
|
exports.stateToString = (state) ->
|
||||||
# the rest with device specs.
|
percentage = _.str.lpad(state.percentage, 3, '0') + '%'
|
||||||
'Raspberry Pi'
|
result = "#{chalk.blue(percentage)} #{chalk.cyan(state.operation.command)}"
|
||||||
'Raspberry Pi 2'
|
|
||||||
'BeagleBone Black'
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.confirm = (yesOption, message) ->
|
switch state.operation.command
|
||||||
Promise.try ->
|
when 'copy'
|
||||||
return true if yesOption
|
return "#{result} #{state.operation.from.path} -> #{state.operation.to.path}"
|
||||||
return form.ask
|
when 'replace'
|
||||||
message: message
|
return "#{result} #{state.operation.file.path}, #{state.operation.copy} -> #{state.operation.replace}"
|
||||||
type: 'confirm'
|
when 'run-script'
|
||||||
default: false
|
return "#{result} #{state.operation.script}"
|
||||||
.then (confirmed) ->
|
|
||||||
if not confirmed
|
|
||||||
throw new Error('Aborted')
|
|
||||||
|
|
||||||
exports.selectApplication = ->
|
|
||||||
resin.models.application.hasAny().then (hasAnyApplications) ->
|
|
||||||
return if not hasAnyApplications
|
|
||||||
resin.models.application.getAll().then (applications) ->
|
|
||||||
applications = _.pluck(applications, 'app_name')
|
|
||||||
applications.unshift
|
|
||||||
name: 'Create a new application'
|
|
||||||
value: null
|
|
||||||
|
|
||||||
return form.ask
|
|
||||||
message: 'Select an application'
|
|
||||||
type: 'list'
|
|
||||||
choices: applications
|
|
||||||
.then (application) ->
|
|
||||||
return application if application?
|
|
||||||
form.ask
|
|
||||||
message: 'Choose a Name for your new application'
|
|
||||||
type: 'input'
|
|
||||||
|
|
||||||
exports.selectProjectDirectory = ->
|
|
||||||
resin.settings.get('projectsDirectory').then (projectsDirectory) ->
|
|
||||||
return form.ask
|
|
||||||
message: 'Please choose a directory for your code'
|
|
||||||
type: 'input'
|
|
||||||
default: projectsDirectory
|
|
||||||
|
|
||||||
exports.awaitDevice = (uuid) ->
|
|
||||||
spinner = new visuals.Spinner("Awaiting device: #{uuid}")
|
|
||||||
|
|
||||||
poll = ->
|
|
||||||
resin.models.device.isOnline(uuid).then (isOnline) ->
|
|
||||||
if isOnline
|
|
||||||
spinner.stop()
|
|
||||||
console.info("Device became online: #{uuid}")
|
|
||||||
return
|
|
||||||
else
|
else
|
||||||
|
throw new Error("Unsupported operation: #{state.operation.type}")
|
||||||
# Spinner implementation is smart enough to
|
|
||||||
# not start again if it was already started
|
|
||||||
spinner.start()
|
|
||||||
|
|
||||||
return Promise.delay(3000).then(poll)
|
|
||||||
|
|
||||||
resin.models.device.getName(uuid).then (deviceName) ->
|
|
||||||
console.info("Waiting for #{deviceName} to connect to resin...")
|
|
||||||
poll().return(uuid)
|
|
||||||
|
94
lib/utils/patterns.coffee
Normal file
94
lib/utils/patterns.coffee
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
_ = require('lodash')
|
||||||
|
Promise = require('bluebird')
|
||||||
|
form = require('resin-cli-form')
|
||||||
|
visuals = require('resin-cli-visuals')
|
||||||
|
resin = require('resin-sdk')
|
||||||
|
manager = require('resin-image-manager')
|
||||||
|
helpers = require('./helpers')
|
||||||
|
|
||||||
|
exports.selectDeviceType = ->
|
||||||
|
resin.models.device.getSupportedDeviceTypes().then (deviceTypes) ->
|
||||||
|
return form.ask
|
||||||
|
message: 'Device Type'
|
||||||
|
type: 'list'
|
||||||
|
choices: deviceTypes
|
||||||
|
|
||||||
|
exports.confirm = (yesOption, message) ->
|
||||||
|
Promise.try ->
|
||||||
|
return true if yesOption
|
||||||
|
return form.ask
|
||||||
|
message: message
|
||||||
|
type: 'confirm'
|
||||||
|
default: false
|
||||||
|
.then (confirmed) ->
|
||||||
|
if not confirmed
|
||||||
|
throw new Error('Aborted')
|
||||||
|
|
||||||
|
exports.selectApplication = ->
|
||||||
|
resin.models.application.hasAny().then (hasAnyApplications) ->
|
||||||
|
return if not hasAnyApplications
|
||||||
|
resin.models.application.getAll().then (applications) ->
|
||||||
|
applications = _.pluck(applications, 'app_name')
|
||||||
|
applications.unshift
|
||||||
|
name: 'Create a new application'
|
||||||
|
value: null
|
||||||
|
|
||||||
|
return form.ask
|
||||||
|
message: 'Select an application'
|
||||||
|
type: 'list'
|
||||||
|
choices: applications
|
||||||
|
.then (application) ->
|
||||||
|
return application if application?
|
||||||
|
form.ask
|
||||||
|
message: 'Choose a Name for your new application'
|
||||||
|
type: 'input'
|
||||||
|
|
||||||
|
exports.selectProjectDirectory = ->
|
||||||
|
resin.settings.get('projectsDirectory').then (projectsDirectory) ->
|
||||||
|
return form.ask
|
||||||
|
message: 'Please choose a directory for your code'
|
||||||
|
type: 'input'
|
||||||
|
default: projectsDirectory
|
||||||
|
|
||||||
|
exports.awaitDevice = (uuid) ->
|
||||||
|
spinner = new visuals.Spinner("Awaiting device: #{uuid}")
|
||||||
|
|
||||||
|
poll = ->
|
||||||
|
resin.models.device.isOnline(uuid).then (isOnline) ->
|
||||||
|
if isOnline
|
||||||
|
spinner.stop()
|
||||||
|
console.info("Device became online: #{uuid}")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
|
||||||
|
# Spinner implementation is smart enough to
|
||||||
|
# not start again if it was already started
|
||||||
|
spinner.start()
|
||||||
|
|
||||||
|
return Promise.delay(3000).then(poll)
|
||||||
|
|
||||||
|
resin.models.device.getName(uuid).then (deviceName) ->
|
||||||
|
console.info("Waiting for #{deviceName} to connect to resin...")
|
||||||
|
poll().return(uuid)
|
||||||
|
|
||||||
|
exports.askDeviceOptions = (deviceType) ->
|
||||||
|
resin.models.config.getDeviceOptions(deviceType).then(form.run)
|
||||||
|
.then (answers) ->
|
||||||
|
answers.os ?= helpers.getOperatingSystem()
|
||||||
|
return answers
|
||||||
|
|
||||||
|
exports.download = (deviceType) ->
|
||||||
|
manager.get(deviceType).then (stream) ->
|
||||||
|
bar = new visuals.Progress('Downloading Device OS')
|
||||||
|
spinner = new visuals.Spinner('Downloading Device OS (size unknown)')
|
||||||
|
|
||||||
|
stream.on 'progress', (state) ->
|
||||||
|
if state?
|
||||||
|
bar.update(state)
|
||||||
|
else
|
||||||
|
spinner.start()
|
||||||
|
|
||||||
|
stream.on 'end', ->
|
||||||
|
spinner.stop()
|
||||||
|
|
||||||
|
return manager.pipeTemporal(stream)
|
13
package.json
13
package.json
@ -38,12 +38,11 @@
|
|||||||
"sinon-chai": "^2.8.0"
|
"sinon-chai": "^2.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^1.3.0",
|
|
||||||
"bluebird": "^2.9.34",
|
"bluebird": "^2.9.34",
|
||||||
"capitano": "~1.7.0",
|
"capitano": "~1.7.0",
|
||||||
|
"chalk": "^1.1.1",
|
||||||
"coffee-script": "^1.9.3",
|
"coffee-script": "^1.9.3",
|
||||||
"columnify": "^1.5.2",
|
"columnify": "^1.5.2",
|
||||||
"html-to-text": "^1.3.1",
|
|
||||||
"is-root": "^1.0.0",
|
"is-root": "^1.0.0",
|
||||||
"lodash": "^3.10.0",
|
"lodash": "^3.10.0",
|
||||||
"mkdirp": "~0.5.0",
|
"mkdirp": "~0.5.0",
|
||||||
@ -52,16 +51,18 @@
|
|||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"resin-cli-events": "^1.0.2",
|
"resin-cli-events": "^1.0.2",
|
||||||
"resin-cli-form": "^1.2.1",
|
"resin-cli-form": "^1.2.1",
|
||||||
"resin-cli-visuals": "^1.1.0",
|
"resin-cli-visuals": "^1.2.2",
|
||||||
"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-device-init": "^1.0.4",
|
||||||
"resin-image": "^1.1.4",
|
"resin-image": "^1.1.4",
|
||||||
"resin-image-manager": "^2.0.0",
|
"resin-image-manager": "^3.2.2",
|
||||||
"resin-pine": "^1.3.0",
|
"resin-pine": "^1.3.0",
|
||||||
"resin-register-device": "^1.0.1",
|
|
||||||
"resin-sdk": "^2.7.2",
|
"resin-sdk": "^2.7.2",
|
||||||
|
"resin-settings-client": "^3.1.0",
|
||||||
"resin-vcs": "^2.0.0",
|
"resin-vcs": "^2.0.0",
|
||||||
"selfupdate": "^1.1.0",
|
"rimraf": "^2.4.3",
|
||||||
|
"umount": "^1.1.1",
|
||||||
"underscore.string": "^3.1.1",
|
"underscore.string": "^3.1.1",
|
||||||
"update-notifier": "^0.5.0",
|
"update-notifier": "^0.5.0",
|
||||||
"valid-email": "0.0.2"
|
"valid-email": "0.0.2"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user