From dc030f4cd16fe73f03c84009ebcdd4fc4207668f Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 7 May 2015 12:40:12 -0300 Subject: [PATCH] Implement Quickstart command --- build/actions/app.js | 10 ++-- build/actions/device.js | 32 +++++++++- build/actions/index.js | 1 + build/actions/wizard.js | 110 ++++++++++++++++++++++++++++++++++ build/app.js | 2 + lib/actions/app.coffee | 16 +++-- lib/actions/device.coffee | 29 ++++++++- lib/actions/index.coffee | 1 + lib/actions/wizard.coffee | 122 ++++++++++++++++++++++++++++++++++++++ lib/app.coffee | 3 + package.json | 12 ++-- 11 files changed, 320 insertions(+), 18 deletions(-) create mode 100644 build/actions/wizard.js create mode 100644 lib/actions/wizard.coffee diff --git a/build/actions/app.js b/build/actions/app.js index c25f9b60..ad26b6ad 100644 --- a/build/actions/app.js +++ b/build/actions/app.js @@ -41,12 +41,10 @@ if (options.type != null) { return callback(null, options.type); } - return resin.models.device.getSupportedDeviceTypes().then(function(supportedDeviceTypes) { - return form.ask({ - message: 'Device Type', - type: 'list', - choices: supportedDeviceTypes - }); + return form.ask({ + message: 'Device Type', + type: 'list', + choices: ['Raspberry Pi', 'Raspberry Pi 2', 'BeagleBone Black'] }).nodeify(callback); }, function(type, callback) { options.type = type; diff --git a/build/actions/device.js b/build/actions/device.js index 2ea06090..059bb2ab 100644 --- a/build/actions/device.js +++ b/build/actions/device.js @@ -1,5 +1,5 @@ (function() { - var _, async, capitano, commandOptions, deviceConfig, drivelist, form, fse, image, inject, manager, path, pine, registerDevice, resin, tmp, vcs, visuals; + var _, async, capitano, commandOptions, deviceConfig, drivelist, form, fse, htmlToText, image, inject, manager, os, path, pine, registerDevice, resin, tmp, vcs, visuals; fse = require('fs-extra'); @@ -35,6 +35,10 @@ drivelist = require('drivelist'); + htmlToText = require('html-to-text'); + + os = require('os'); + tmp.setGracefulCleanup(); commandOptions = require('./command-options'); @@ -291,6 +295,7 @@ } }, 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) { @@ -310,7 +315,7 @@ } bar = new visuals.Progress('Downloading Device OS'); spinner = new visuals.Spinner('Downloading Device OS (size unknown)'); - return manager.configure(results.manifest, results.config, function(error, imagePath, removeCallback) { + return manager.configure(params.manifest, results.config, function(error, imagePath, removeCallback) { spinner.stop(); return callback(error, imagePath, removeCallback); }, function(state) { @@ -322,6 +327,7 @@ }); }, function(configuredImagePath, removeCallback, callback) { var bar; + 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'); return image.write({ @@ -342,6 +348,28 @@ }, function(device, callback) { console.info("Device created: " + device.name); return callback(null, device.name); + }, function(deviceName, callback) { + var instructions, osSpecificInstructions, platform, platformHash; + if (params.manifest.instructions == null) { + instructions = ''; + } + if (_.isArray(params.manifest.instructions)) { + instructions = htmlToText.fromString(params.manifest.instructions.join('\n')); + } + platformHash = { + darwin: 'osx', + linux: 'linux', + win32: 'windows' + }; + platform = platformHash[os.platform()]; + osSpecificInstructions = params.manifest.instructions[platform]; + if (osSpecificInstructions == null) { + instructions = ''; + } else { + instructions = htmlToText.fromString(osSpecificInstructions.join('\n')); + } + console.log('\n' + instructions); + return callback(null, params.uuid); } ], done); } diff --git a/build/actions/index.js b/build/actions/index.js index f09ebe6b..7f71e44b 100644 --- a/build/actions/index.js +++ b/build/actions/index.js @@ -1,5 +1,6 @@ (function() { module.exports = { + wizard: require('./wizard'), app: require('./app'), info: require('./info'), auth: require('./auth'), diff --git a/build/actions/wizard.js b/build/actions/wizard.js new file mode 100644 index 00000000..fd91fdff --- /dev/null +++ b/build/actions/wizard.js @@ -0,0 +1,110 @@ +(function() { + var Promise, _, async, capitano, form, mkdirp, path, resin, userHome, visuals; + + _ = require('lodash-contrib'); + + Promise = require('bluebird'); + + capitano = Promise.promisifyAll(require('capitano')); + + path = require('path'); + + mkdirp = require('mkdirp'); + + userHome = require('user-home'); + + visuals = require('resin-cli-visuals'); + + async = require('async'); + + resin = require('resin-sdk'); + + form = require('resin-cli-form'); + + exports.wizard = { + signature: 'quickstart [name]', + description: 'getting started with resin.io', + help: 'Use this command to run a friendly wizard to get started with resin.io.\n\nThe wizard will guide you through:\n\n - Create an application.\n - Initialise an SDCard with the resin.io operating system.\n - Associate an existing project directory with your resin.io application.\n - Push your project to your devices.\n\nExamples:\n\n $ sudo resin quickstart\n $ sudo resin quickstart MyApp', + root: true, + permission: 'user', + action: function(params, options, done) { + return async.waterfall([ + function(callback) { + if (params.name != null) { + return callback(); + } + return async.waterfall([ + function(callback) { + return resin.models.application.hasAny().nodeify(callback); + }, function(hasAnyApplications, callback) { + if (!hasAnyApplications) { + return callback(null, null); + } + return async.waterfall([ + function(callback) { + return resin.models.application.getAll().nodeify(callback); + }, function(applications, callback) { + 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 + }).nodeify(callback); + } + ], callback); + }, function(application, callback) { + if (application != null) { + return callback(null, application); + } + return form.ask({ + message: 'Choose a Name for your new application', + type: 'input' + }).then(function(applicationName) { + return capitano.runAsync("app create " + applicationName)["return"](applicationName); + }).nodeify(callback); + }, function(applicationName, callback) { + params.name = applicationName; + return callback(); + } + ], callback); + }, function(callback) { + return capitano.run("device init --application " + params.name, callback); + }, function(deviceUuid, callback) { + params.uuid = deviceUuid; + return resin.models.device.getName(params.uuid).then(function(deviceName) { + params.deviceName = deviceName; + console.log("Waiting for " + params.deviceName + " to connect to resin..."); + return capitano.runAsync("device await " + params.uuid)["return"](callback); + }).nodeify(callback); + }, function(callback) { + console.log("The device " + params.deviceName + " successfully connected to resin!"); + console.log(''); + return capitano.run("device " + params.uuid, callback); + }, function(callback) { + console.log('Your device is ready, lets start pushing some code!'); + return form.ask({ + message: 'Please choose a directory for your code', + type: 'input', + "default": path.join(userHome, 'ResinProjects', params.name) + }).nodeify(callback); + }, function(directoryName, callback) { + params.directory = directoryName; + return mkdirp(directoryName, callback); + }, function(made, callback) { + console.log("Associating " + params.name + " with " + params.directory + "..."); + process.chdir(params.directory); + return capitano.run("app associate " + params.name + " --project " + params.directory, callback); + }, function(remoteUrl, callback) { + console.log("Resin git remote added: " + remoteUrl); + console.log('Please type "git push resin master" into your project directory now!'); + return callback(); + } + ], done); + } + }; + +}).call(this); diff --git a/build/app.js b/build/app.js index a10d91cf..a2a62d96 100644 --- a/build/app.js +++ b/build/app.js @@ -65,6 +65,8 @@ capitano.command(actions.help.help); + capitano.command(actions.wizard.wizard); + capitano.command(actions.auth.login); capitano.command(actions.auth.logout); diff --git a/lib/actions/app.coffee b/lib/actions/app.coffee index 29d08f0a..9fa78e69 100644 --- a/lib/actions/app.coffee +++ b/lib/actions/app.coffee @@ -45,11 +45,17 @@ exports.create = return callback(new Error('You already have an application with that name!')) return callback(null, options.type) if options.type? - resin.models.device.getSupportedDeviceTypes().then (supportedDeviceTypes) -> - form.ask - message: 'Device Type' - type: 'list' - choices: supportedDeviceTypes + form.ask + message: 'Device Type' + type: 'list' + choices: [ + + # Lock to specific devices until we support + # the rest with device specs. + 'Raspberry Pi' + 'Raspberry Pi 2' + 'BeagleBone Black' + ] .nodeify(callback) (type, callback) -> diff --git a/lib/actions/device.coffee b/lib/actions/device.coffee index 3bcf7f6d..a99947d5 100644 --- a/lib/actions/device.coffee +++ b/lib/actions/device.coffee @@ -15,6 +15,8 @@ tmp = require('tmp') deviceConfig = require('resin-device-config') form = require('resin-cli-form') drivelist = require('drivelist') +htmlToText = require('html-to-text') +os = require('os') # Cleanup the temporary files even when an uncaught exception occurs tmp.setGracefulCleanup() @@ -365,6 +367,7 @@ exports.init = , callback (results, callback) -> + params.manifest = results.manifest console.info('Associating the device') registerDevice.register pine, results.config, (error, device) -> @@ -388,7 +391,7 @@ exports.init = bar = new visuals.Progress('Downloading Device OS') spinner = new visuals.Spinner('Downloading Device OS (size unknown)') - manager.configure results.manifest, results.config, (error, imagePath, removeCallback) -> + manager.configure params.manifest, results.config, (error, imagePath, removeCallback) -> spinner.stop() return callback(error, imagePath, removeCallback) , (state) -> @@ -398,6 +401,8 @@ exports.init = 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') @@ -420,4 +425,26 @@ exports.init = console.info("Device created: #{device.name}") return callback(null, device.name) + (deviceName, callback) -> + instructions = '' if not params.manifest.instructions? + + if _.isArray(params.manifest.instructions) + instructions = htmlToText.fromString(params.manifest.instructions.join('\n')) + + platformHash = + darwin: 'osx' + linux: 'linux' + win32: 'windows' + + platform = platformHash[os.platform()] + osSpecificInstructions = params.manifest.instructions[platform] + + if not osSpecificInstructions? + instructions = '' + else + instructions = htmlToText.fromString(osSpecificInstructions.join('\n')) + + console.log('\n' + instructions) + return callback(null, params.uuid) + ], done diff --git a/lib/actions/index.coffee b/lib/actions/index.coffee index eed70bb8..311fd5bb 100644 --- a/lib/actions/index.coffee +++ b/lib/actions/index.coffee @@ -1,4 +1,5 @@ module.exports = + wizard: require('./wizard') app: require('./app') info: require('./info') auth: require('./auth') diff --git a/lib/actions/wizard.coffee b/lib/actions/wizard.coffee new file mode 100644 index 00000000..79af3ce9 --- /dev/null +++ b/lib/actions/wizard.coffee @@ -0,0 +1,122 @@ +_ = require('lodash-contrib') +Promise = require('bluebird') +capitano = Promise.promisifyAll(require('capitano')) +path = require('path') +mkdirp = require('mkdirp') +userHome = require('user-home') +visuals = require('resin-cli-visuals') +async = require('async') +resin = require('resin-sdk') +form = require('resin-cli-form') + +exports.wizard = + signature: 'quickstart [name]' + description: 'getting started with resin.io' + help: ''' + Use this command to run a friendly wizard to get started with resin.io. + + The wizard will guide you through: + + - Create an application. + - Initialise an SDCard with the resin.io operating system. + - Associate an existing project directory with your resin.io application. + - Push your project to your devices. + + Examples: + + $ sudo resin quickstart + $ sudo resin quickstart MyApp + ''' + root: true + permission: 'user' + action: (params, options, done) -> + async.waterfall [ + + (callback) -> + return callback() if params.name? + + # TODO: Move this whole routine to Resin CLI Visuals + async.waterfall [ + + (callback) -> + resin.models.application.hasAny().nodeify(callback) + + (hasAnyApplications, callback) -> + return callback(null, null) if not hasAnyApplications + + async.waterfall [ + + (callback) -> + resin.models.application.getAll().nodeify(callback) + + (applications, callback) -> + applications = _.pluck(applications, 'app_name') + applications.unshift + name: 'Create a new application' + value: null + + form.ask + message: 'Select an application' + type: 'list' + choices: applications + .nodeify(callback) + + ], callback + + (application, callback) -> + return callback(null, application) if application? + + form.ask + message: 'Choose a Name for your new application' + type: 'input' + .then (applicationName) -> + capitano.runAsync("app create #{applicationName}").return(applicationName) + .nodeify(callback) + + (applicationName, callback) -> + params.name = applicationName + return callback() + + ], callback + + (callback) -> + capitano.run("device init --application #{params.name}", callback) + + (deviceUuid, callback) -> + params.uuid = deviceUuid + resin.models.device.getName(params.uuid).then (deviceName) -> + params.deviceName = deviceName + console.log("Waiting for #{params.deviceName} to connect to resin...") + capitano.runAsync("device await #{params.uuid}").return(callback) + .nodeify(callback) + + (callback) -> + console.log("The device #{params.deviceName} successfully connected to resin!") + console.log('') + capitano.run("device #{params.uuid}", callback) + + (callback) -> + console.log('Your device is ready, lets start pushing some code!') + form.ask + message: 'Please choose a directory for your code' + type: 'input' + + # TODO: Move this to resin-settings-client. + default: path.join(userHome, 'ResinProjects', params.name) + .nodeify(callback) + + (directoryName, callback) -> + params.directory = directoryName + mkdirp(directoryName, callback) + + (made, callback) -> + console.log("Associating #{params.name} with #{params.directory}...") + process.chdir(params.directory) + capitano.run("app associate #{params.name} --project #{params.directory}", callback) + + (remoteUrl, callback) -> + console.log("Resin git remote added: #{remoteUrl}") + console.log('Please type "git push resin master" into your project directory now!') + return callback() + + ], done diff --git a/lib/app.coffee b/lib/app.coffee index 936a37d2..a11e33e8 100644 --- a/lib/app.coffee +++ b/lib/app.coffee @@ -51,6 +51,9 @@ capitano.command(actions.info.config) # ---------- Help Module ---------- capitano.command(actions.help.help) +# ---------- Wizard Module ---------- +capitano.command(actions.wizard.wizard) + # ---------- Auth Module ---------- capitano.command(actions.auth.login) capitano.command(actions.auth.logout) diff --git a/package.json b/package.json index 42d04422..ff7e7515 100644 --- a/package.json +++ b/package.json @@ -42,11 +42,13 @@ }, "dependencies": { "async": "^1.3.0", - "capitano": "~1.6.0", + "bluebird": "^2.9.34", + "capitano": "~1.6.1", "coffee-script": "^1.9.3", "conf.js": "^0.1.1", "drivelist": "^1.2.2", "fs-extra": "^0.22.1", + "html-to-text": "^1.3.1", "lodash": "^3.10.0", "lodash-contrib": "^393.0.1", "mkdirp": "~0.5.0", @@ -57,8 +59,8 @@ "resin-cli-visuals": "^1.0.0", "resin-config-inject": "^2.0.0", "resin-device-config": "^1.0.0", - "resin-image": "^1.1.3", - "resin-image-manager": "^1.1.0", + "resin-image": "^1.1.4", + "resin-image-manager": "^2.0.0", "resin-pine": "^1.3.0", "resin-register-device": "^1.0.1", "resin-sdk": "^2.2.0", @@ -67,6 +69,8 @@ "selfupdate": "^1.1.0", "through2": "^2.0.0", "tmp": "0.0.26", - "underscore.string": "^3.1.1" + "underscore.string": "^3.1.1", + "update-notifier": "^0.3.1", + "user-home": "^2.0.0" } }