mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
314 Commits
Author | SHA1 | Date | |
---|---|---|---|
9260c8dce2 | |||
bea5f22732 | |||
363f12f81b | |||
1cbd33679f | |||
e962371b59 | |||
77a3d4d6f6 | |||
fc5fe6cf68 | |||
f921488e8c | |||
8fe08642f5 | |||
822632718f | |||
f66cd00646 | |||
5498e45a35 | |||
38479b3191 | |||
965fd8fc19 | |||
7a4f551a47 | |||
303fd74012 | |||
6a7d1b0c70 | |||
c1e6a28640 | |||
ec72f93480 | |||
8913fb515b | |||
6f91ff898f | |||
aa949103f6 | |||
14bd4ad74e | |||
f2507daa09 | |||
ff81c1e514 | |||
7ce92b47a0 | |||
bde5cc65da | |||
ec9347c3a4 | |||
ceb8dada1d | |||
f4186acf80 | |||
05ce54eac1 | |||
d28ecf3230 | |||
8562f723c5 | |||
f6d2043747 | |||
485818f3b5 | |||
ec28bd9c9e | |||
ad68dcf692 | |||
0b7e2a2c8c | |||
b6ebd0631a | |||
7ace4bdfa6 | |||
1cfbd4197d | |||
b2425d2c0e | |||
0ba914236a | |||
71ee0a6cf7 | |||
4326ad4d9c | |||
055bac6ff4 | |||
58713dc291 | |||
adf4aef517 | |||
a17bb2cc09 | |||
48d620f7cd | |||
fa7b104762 | |||
faf2fa167f | |||
ad1e68427c | |||
e9147f0f6f | |||
cddf630907 | |||
494a286cae | |||
e5e871ddcd | |||
5a3166e3de | |||
3149464c7a | |||
97d9b7816f | |||
ec77437080 | |||
d65882639a | |||
f8470287c1 | |||
3cc41ed62a | |||
445e37ccaf | |||
ed6427c541 | |||
90be01b05d | |||
1124293b9a | |||
0e804fdfd8 | |||
0443a35f2b | |||
3a148217e0 | |||
79d1892b66 | |||
0e06ac464f | |||
5ae83d8337 | |||
8694ee2c59 | |||
8234f7675a | |||
15cb0c4889 | |||
a3ebd9827f | |||
30d84f015a | |||
686414b03d | |||
6377618c12 | |||
f17e9c97b8 | |||
21fcdfaff6 | |||
4072edcced | |||
d704c10197 | |||
dea2a7f055 | |||
7e6eb4b9e4 | |||
61474fba5c | |||
42256384be | |||
e25f232fe5 | |||
f6d8f12ba2 | |||
d2b9e6fd8c | |||
cf7effacc0 | |||
1eb7aba293 | |||
21e916679c | |||
d574743c21 | |||
d8a2b82662 | |||
f4ebd890df | |||
b0be5de83a | |||
ba21ddd010 | |||
eecd7a0fd8 | |||
eb4c2f62a7 | |||
dc1e5e6512 | |||
adc0b183cd | |||
828b4f73d1 | |||
82a0761f49 | |||
904b9f07fb | |||
3c0acaa7da | |||
64c8420c9d | |||
26e3dc1aa7 | |||
3bc22a7b7c | |||
7e5b2695fe | |||
79afa79fd9 | |||
cdaaddb826 | |||
ec5f6a7cd8 | |||
72f34031a9 | |||
dc257b5cab | |||
4bdcd3d2ee | |||
b0650530cc | |||
454669ada2 | |||
a65975596e | |||
91f878cfc0 | |||
8c3e832cdc | |||
a5c670902d | |||
14be1d5f9f | |||
41d6d5c670 | |||
a090e6c21d | |||
d78c1ffa47 | |||
5808d20d88 | |||
7f4065e3da | |||
07daa51051 | |||
d10d4ce185 | |||
7f763e7881 | |||
5de0f66d7a | |||
354921ca92 | |||
fb28cc7d2f | |||
7cf89a9bf6 | |||
a3cbc549d8 | |||
1c48328347 | |||
9b69fe3c3c | |||
dc513a08f6 | |||
fcc44949a7 | |||
006764af66 | |||
b879d3f9ea | |||
7f4863da86 | |||
ba6f50d171 | |||
a803d4f646 | |||
85d940df66 | |||
6f9535ca34 | |||
019e2ac357 | |||
433916e18a | |||
f19588032f | |||
0595452c3d | |||
d6305df48e | |||
f7084580b2 | |||
3dd5f5858a | |||
02a06e1e7c | |||
50daf8ef73 | |||
fd5a34a1c4 | |||
51fda13684 | |||
a698b25fda | |||
89bd861d8e | |||
e5b7aae4ae | |||
031168ceed | |||
09a5788902 | |||
713664d103 | |||
f63391acf9 | |||
79ee4302ec | |||
9adda22921 | |||
25311f2a18 | |||
70c060b124 | |||
7a8a3c851b | |||
1096b2d212 | |||
ee286c5690 | |||
1da1d2e6fc | |||
64be9f936d | |||
30f24333c0 | |||
30f6a78282 | |||
8c9a0e0ff1 | |||
8268bbf700 | |||
e712e2f266 | |||
0807b6a2d9 | |||
83382cc8f7 | |||
8401aaeae2 | |||
abf5740950 | |||
606777508d | |||
e9ec6c67b2 | |||
69566f7fc3 | |||
6e4b299c7d | |||
ef35ebf79d | |||
1bc78edf71 | |||
d5204a09f7 | |||
4647aa70c0 | |||
2e8ec3ac64 | |||
25c6246e9f | |||
5408938007 | |||
50cb04b6f7 | |||
09fe4b11ad | |||
4157f21e06 | |||
4900230881 | |||
e60c0605e5 | |||
085781fa18 | |||
08ad8bd5d7 | |||
3d36e5f5d3 | |||
57319f26a6 | |||
11033683fd | |||
e5166c6c8e | |||
5c96663d1e | |||
ffb48c8669 | |||
12145a2393 | |||
f379866c1c | |||
dc030f4cd1 | |||
b726a2d778 | |||
a715ec9dc1 | |||
d24b871964 | |||
bd0c4e6034 | |||
95637e5608 | |||
93b394ed76 | |||
b515e427ff | |||
26b1acf5ef | |||
f31eb7c2b5 | |||
d423a6ea24 | |||
4211333e4e | |||
f220e380a7 | |||
9564b4e478 | |||
e2125b8ce9 | |||
0bb0e6ea4b | |||
cf512cc01b | |||
fe3e68c3af | |||
0bbfbe36c7 | |||
d6ae689593 | |||
5b5d1be52f | |||
cb808869dd | |||
64d83dccfb | |||
122253bb25 | |||
1d53db2854 | |||
30dc5ea1ea | |||
58c7ff1f1b | |||
57b9d634be | |||
6fb25dea88 | |||
10478e389a | |||
46fa4ee2a2 | |||
a7cefe44bf | |||
3d03585318 | |||
076c3428ee | |||
9d4ac46985 | |||
24edb867fa | |||
e926ac46c9 | |||
9ffd657dbb | |||
aa3cb39551 | |||
0acbdac66f | |||
5619bdbb67 | |||
381e63bfc9 | |||
e779347ff2 | |||
8fa906dd48 | |||
7e5ecd634d | |||
29cf4c1e89 | |||
73736abdea | |||
9ab411bade | |||
ef33156de7 | |||
6d8fd6e547 | |||
bab90a8bf2 | |||
43f0288c6c | |||
47e6371e2e | |||
5e400ed335 | |||
56a78b57af | |||
2bfeb7f42c | |||
37e0f12f89 | |||
fdd0e4a966 | |||
38df7e0038 | |||
e9efb78280 | |||
0424d7b640 | |||
2b80c7c91f | |||
142e2c0a65 | |||
1ed9ae7d60 | |||
329bf25dbd | |||
e18ffba183 | |||
ae3f0b429d | |||
34736c4e9b | |||
806678ee5f | |||
1e70401bdd | |||
3c7615f20d | |||
054d5e4879 | |||
b04ed43bed | |||
c2bbb952c5 | |||
63a9e1859b | |||
68ef069e6a | |||
72d4b21cb4 | |||
e8b931680d | |||
218b407f30 | |||
773cc27145 | |||
1c76e2e15b | |||
e466cfd6ff | |||
992b04d521 | |||
5c16c86871 | |||
78af9bbb10 | |||
b78c48d89f | |||
1f8c610bb0 | |||
d220728380 | |||
db58e9986c | |||
2a5e66eca6 | |||
e7e8ec296c | |||
80e69c56d0 | |||
ce3c6aecff | |||
300f76ba83 | |||
a4e8a38bf4 | |||
9bb04f43a8 | |||
9b0c08bd46 | |||
679d48e86e | |||
4b6bf60531 | |||
d8ce6648e2 | |||
402bc2d3a8 | |||
f3e193be0f | |||
f5b461612b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,3 +33,4 @@ release/build
|
||||
|
||||
npm-shrinkwrap.json
|
||||
.resinconf
|
||||
resinrc.yml
|
||||
|
@ -1,5 +1,5 @@
|
||||
coffee_script:
|
||||
config_file: coffeelint.json
|
||||
|
||||
java_script:
|
||||
javascript:
|
||||
enabled: false
|
||||
|
@ -1,4 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.12"
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
|
74
CHANGELOG.md
Normal file
74
CHANGELOG.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.0.1] - 2015-10-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix critical error when elevating permissions.
|
||||
|
||||
## [2.0.0] - 2015-10-26
|
||||
|
||||
### Added
|
||||
|
||||
- Add `drive` option to `os initialize`.
|
||||
- Add application name length validation.
|
||||
- Allow passing a custom uuid to `device register`.
|
||||
- Add `advanced` option in `device init`.
|
||||
- Implement user/password login form with 2FA support.
|
||||
|
||||
### Changed
|
||||
|
||||
- Clarify the need for admin privileges on update.
|
||||
- Fix non working `yes` option in `os initialize`.
|
||||
- Fix non working `type` option in `app create`.
|
||||
- Take device type as an option in `os initialize`.
|
||||
- Improve the way the update notifier is shown.
|
||||
- Ignore advanced configuration options by default.
|
||||
- Fix `device info` shadowing other command help pages.
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove login with token functionality.
|
||||
- Remove project directory creation logic in `device init`.
|
||||
- Remove `app associate` command.
|
||||
|
||||
## [1.1.0] - 2015-10-13
|
||||
|
||||
### Added
|
||||
|
||||
- Implement `os download` command.
|
||||
- Implement `os configure` command.
|
||||
- Implement `os initialize` command.
|
||||
- Implement `device register` command.
|
||||
- Add TROUBLESHOOTING guide.
|
||||
- Add a dynamic widget for selecting connected drives.
|
||||
|
||||
### Changed
|
||||
|
||||
- Make sure temporary files are removed on failures in `device init`.
|
||||
- Prompt to select application if running `device init` with no arguments.
|
||||
- Only use admin privileges in `os initialize` to avoid the cache directory to belong to `root`.
|
||||
- Separate help output per relevance.
|
||||
- Only print primary command help by default.
|
||||
- Print plugin warnings in red as the rest of the errors.
|
||||
- Shorten the length of device await message.
|
||||
- Upgrade Resin SDK to v3.0.0.
|
||||
- Check that a directory is a `git` repository before attempting to run `git` operations on it.
|
||||
- Improve plugin scanning mechanism.
|
||||
- Fix `EPERM` issue in Windows after a successfull `device init`.
|
||||
- Fix SDCard burning issues in Windows 10.
|
||||
- Fix plugins not being loaded in Windows 10.
|
||||
- Fix bug when listing the available drives in Windows when the user name contains spaces.
|
||||
- Fix an edge case that caused drives to be scanned incorrectly in OS X.
|
||||
- Fix operating system not being detected correctly when initializing a device.
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove outdated information from README.
|
||||
|
||||
[2.0.1]: https://github.com/resin-io/resin-cli/compare/v2.0.0...v2.0.1
|
||||
[2.0.0]: https://github.com/resin-io/resin-cli/compare/v1.1.0...v2.0.0
|
||||
[1.1.0]: https://github.com/resin-io/resin-cli/compare/v1.0.0...v1.1.0
|
65
README.md
65
README.md
@ -1,65 +1,56 @@
|
||||
# Resin CLI
|
||||
Resin CLI
|
||||
=========
|
||||
|
||||
[](http://badge.fury.io/js/resin-cli)
|
||||
[](https://david-dm.org/resin-io/resin-cli.png)
|
||||
[](https://travis-ci.org/resin-io/resin-cli)
|
||||
[](https://ci.appveyor.com/project/jviotti/resin-cli)
|
||||
|
||||
The official Resin CLI tool.
|
||||
|
||||
## Installing
|
||||
Requisites
|
||||
----------
|
||||
|
||||
- [NodeJS](https://nodejs.org) (at least v0.10)
|
||||
- [Git](https://git-scm.com)
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
### Installing
|
||||
|
||||
This might require elevated privileges in some environments.
|
||||
|
||||
```sh
|
||||
$ npm install -g resin-cli
|
||||
```
|
||||
|
||||
### Running locally
|
||||
### Login
|
||||
|
||||
```sh
|
||||
$ ./bin/resin
|
||||
$ resin login
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
You can run the [Mocha](http://mochajs.org/) test suite, you can do:
|
||||
### List available commands
|
||||
|
||||
```sh
|
||||
$ gulp test
|
||||
$ resin help
|
||||
```
|
||||
|
||||
## Development mode
|
||||
### Run the quickstart wizard
|
||||
|
||||
The following command will watch for any changes and will run a linter and the whole test suite:
|
||||
Run as `root` on UNIX based systems, and in an administrator command line prompt in Windows.
|
||||
|
||||
```sh
|
||||
$ gulp watch
|
||||
$ resin quickstart
|
||||
```
|
||||
|
||||
If you set `DEBUG` environment variable, errors will print with a stack trace:
|
||||
Support
|
||||
-------
|
||||
|
||||
```sh
|
||||
$ DEBUG=true resin ...
|
||||
```
|
||||
If you're having any problem, check our [troubleshooting guide](https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md) and if your problem is not addressed there, please [raise an issue](https://github.com/resin-io/resin-cli/issues/new) on GitHub and the Resin.io team will be happy to help.
|
||||
|
||||
## Documentation
|
||||
License
|
||||
-------
|
||||
|
||||
You can renegerate the documentation with:
|
||||
|
||||
```sh
|
||||
$ npm run-script doc
|
||||
```
|
||||
|
||||
## Manual pages
|
||||
|
||||
UNIX manual pages reside in `man/`
|
||||
|
||||
You can regenerate UNIX `roff` manual pages from markdown with:
|
||||
|
||||
```sh
|
||||
$ gulp man
|
||||
```
|
||||
|
||||
If you add a new `man` page, remember to add the generated filename to the `man` array in `package.json`.
|
||||
|
||||
## Caveats
|
||||
|
||||
- Some interactive widgets don't work on [Cygwin](https://cygwin.com/). If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
|
||||
The project is licensed under the MIT license.
|
||||
|
33
TROUBLESHOOTING.md
Normal file
33
TROUBLESHOOTING.md
Normal file
@ -0,0 +1,33 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
This document contains common issues related to the Resin CLI, and how to fix them.
|
||||
|
||||
### After burning to an sdcard, my device doesn't boot
|
||||
|
||||
- The downloaded image is not complete (download was interrupted).
|
||||
|
||||
Please clean the cache (`%HOME/.resin/cache` or `C:\Users\<user>\_resin\cache`) and run the command again. In the future, the CLI will check that the image is not complete and clean the cache for you.
|
||||
|
||||
### I get a permission error when burning to an sdcard
|
||||
|
||||
- The SDCard is locked.
|
||||
|
||||
### I get EINVAL errors on Cygwin
|
||||
|
||||
The errors look something like this:
|
||||
|
||||
```
|
||||
net.js:156
|
||||
this._handle.open(options.fd);
|
||||
^
|
||||
Error: EINVAL, invalid argument
|
||||
at new Socket (net.js:156:18)
|
||||
at process.stdin (node.js:664:19)
|
||||
at Object.Interface.createInterface (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\node_modules\readline2\index.js:31:43)
|
||||
at PromptUI.UI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\baseUI.js:23:40)
|
||||
at new PromptUI (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\ui\prompt.js:26:8)
|
||||
at Object.promptModule [as prompt] (C:\cygwin\home\Juan Cruz Viotti\Projects\resin-cli\node_modules\inquirer\lib\inquirer.js:27:14)
|
||||
```
|
||||
|
||||
- Some interactive widgets don't work on `Cygwin`. If you're running Windows, it's preferrable that you use `cmd.exe`, as `Cygwin` is [not official supported by Node.js](https://github.com/chjj/blessed/issues/56#issuecomment-42671945).
|
31
appveyor.yml
Normal file
31
appveyor.yml
Normal file
@ -0,0 +1,31 @@
|
||||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.node-gyp
|
||||
- '%AppData%\npm-cache'
|
||||
|
||||
# what combinations to test
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: 0.10
|
||||
- nodejs_version: 0.11
|
||||
- nodejs_version: 0.12
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm -g install npm@2.12.1
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
- npm install -g gulp
|
||||
- npm install
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- ps: gulp test
|
||||
- cmd: gulp test
|
@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var os = require('../build/actions/os');
|
||||
var errors = require('../build/errors');
|
||||
|
||||
// TODO: Do some error handling when image or device are incorrect
|
||||
|
||||
os.install.action({
|
||||
image: process.argv[2],
|
||||
device: process.argv[3]
|
||||
}, {
|
||||
yes: true,
|
||||
fromScript: true
|
||||
}, function(error) {
|
||||
return errors.handle(error);
|
||||
});
|
@ -1,11 +1,5 @@
|
||||
(function() {
|
||||
var _, async, commandOptions, path, resin, vcs, visuals;
|
||||
|
||||
path = require('path');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
async = require('async');
|
||||
var commandOptions, events, patterns, resin, visuals;
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
@ -13,7 +7,9 @@
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
vcs = require('resin-vcs');
|
||||
events = require('resin-cli-events');
|
||||
|
||||
patterns = require('../utils/patterns');
|
||||
|
||||
exports.create = {
|
||||
signature: 'app create <name>',
|
||||
@ -28,27 +24,22 @@
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return resin.models.application.has(params.name, callback);
|
||||
}, function(hasApplication, callback) {
|
||||
if (hasApplication) {
|
||||
return callback(new Error('You already have an application with that name!'));
|
||||
}
|
||||
if (options.type != null) {
|
||||
return callback(null, options.type);
|
||||
}
|
||||
return resin.models.device.getSupportedDeviceTypes(function(error, deviceTypes) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return visuals.widgets.select('Select a type', deviceTypes, callback);
|
||||
});
|
||||
}, function(type, callback) {
|
||||
return resin.models.application.create(params.name, type, callback);
|
||||
return resin.models.application.has(params.name).then(function(hasApplication) {
|
||||
if (hasApplication) {
|
||||
throw new Error('You already have an application with that name!');
|
||||
}
|
||||
], done);
|
||||
}).then(function() {
|
||||
return options.type || patterns.selectDeviceType();
|
||||
}).then(function(deviceType) {
|
||||
return resin.models.application.create(params.name, deviceType);
|
||||
}).then(function(application) {
|
||||
console.info("Application created: " + application.app_name + " (" + application.device_type + ", id " + application.id + ")");
|
||||
return events.send('application.create', {
|
||||
application: application.id
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -57,14 +48,11 @@
|
||||
description: 'list all applications',
|
||||
help: 'Use this command to list all your applications.\n\nNotice this command only shows the most important bits of information for each app.\nIf you want detailed information, use resin app <name> instead.\n\nExamples:\n\n $ resin apps',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.getAll(function(error, applications) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(applications, ['id', 'app_name', 'device_type', 'online_devices', 'devices_length']));
|
||||
return done();
|
||||
});
|
||||
return resin.models.application.getAll().then(function(applications) {
|
||||
return console.log(visuals.table.horizontal(applications, ['id', 'app_name', 'device_type', 'online_devices', 'devices_length']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -73,14 +61,14 @@
|
||||
description: 'list a single application',
|
||||
help: 'Use this command to show detailed information for a single application.\n\nExamples:\n\n $ resin app MyApp',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.get(params.name, function(error, application) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.vertical(application, ['id', 'app_name', 'device_type', 'git_repository', 'commit']));
|
||||
return done();
|
||||
});
|
||||
return resin.models.application.get(params.name).then(function(application) {
|
||||
console.log(visuals.table.vertical(application, ["$" + application.app_name + "$", 'id', 'device_type', 'git_repository', 'commit']));
|
||||
return events.send('application.open', {
|
||||
application: application.id
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -90,7 +78,7 @@
|
||||
help: 'Use this command to restart all devices that belongs to a certain application.\n\nExamples:\n\n $ resin app restart MyApp',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.restart(params.name, done);
|
||||
return resin.models.application.restart(params.name).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -101,66 +89,15 @@
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('application', options.yes, function(callback) {
|
||||
return resin.models.application.remove(params.name, callback);
|
||||
}, done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.associate = {
|
||||
signature: 'app associate <name>',
|
||||
description: 'associate a resin project',
|
||||
help: 'Use this command to associate a project directory with a resin application.\n\nThis command adds a \'resin\' git remote to the directory and runs git init if necessary.\n\nExamples:\n\n $ resin app associate MyApp\n $ resin app associate MyApp --project my/app/directory',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var currentDirectory;
|
||||
currentDirectory = process.cwd();
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return vcs.initialize(currentDirectory, callback);
|
||||
}, function(callback) {
|
||||
return resin.models.application.get(params.name, callback);
|
||||
}, function(application, callback) {
|
||||
return vcs.addRemote(currentDirectory, application.git_repository, callback);
|
||||
}
|
||||
], function(error, remoteUrl) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("git repository added: " + remoteUrl);
|
||||
return done(null, remoteUrl);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = {
|
||||
signature: 'init',
|
||||
description: 'init an application',
|
||||
help: 'Use this command to initialise a directory as a resin application.\n\nThis command performs the following steps:\n - Create a resin.io application.\n - Initialize the current directory as a git repository.\n - Add the corresponding git remote to the application.\n\nExamples:\n\n $ resin init\n $ resin init --project my/app/directory',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var currentDirectory;
|
||||
currentDirectory = process.cwd();
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
var currentDirectoryBasename;
|
||||
currentDirectoryBasename = path.basename(currentDirectory);
|
||||
return visuals.widgets.ask('What is the name of your application?', currentDirectoryBasename, callback);
|
||||
}, function(applicationName, callback) {
|
||||
return exports.create.action({
|
||||
name: applicationName
|
||||
}, options, function(error) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null, applicationName);
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then(function() {
|
||||
return resin.models.application.remove(params.name);
|
||||
}).tap(function() {
|
||||
return resin.models.application.get(params.name).then(function(application) {
|
||||
return events.send('application.delete', {
|
||||
application: application.id
|
||||
});
|
||||
}, function(applicationName, callback) {
|
||||
return exports.associate.action({
|
||||
name: applicationName
|
||||
}, options, callback);
|
||||
}
|
||||
], done);
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,45 +1,69 @@
|
||||
(function() {
|
||||
var TOKEN_URL, _, async, open, resin, settings, url, visuals;
|
||||
var Promise, _, events, form, resin, validation, visuals;
|
||||
|
||||
open = require('open');
|
||||
Promise = require('bluebird');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
url = require('url');
|
||||
|
||||
async = require('async');
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
settings = require('resin-settings-client');
|
||||
form = require('resin-cli-form');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
TOKEN_URL = url.resolve(settings.get('remoteUrl'), '/preferences');
|
||||
events = require('resin-cli-events');
|
||||
|
||||
validation = require('../utils/validation');
|
||||
|
||||
exports.login = {
|
||||
signature: 'login [token]',
|
||||
signature: 'login',
|
||||
description: 'login to resin.io',
|
||||
help: "Use this command to login to your resin.io account.\n\nTo login, you need your token, which is accesible from the preferences page:\n\n " + TOKEN_URL + "\n\nExamples:\n\n $ resin login\n $ resin login \"eyJ0eXAiOiJKV1Qi...\"",
|
||||
help: 'Use this command to login to your resin.io account.\n\nExamples:\n\n $ resin login',
|
||||
options: [
|
||||
{
|
||||
signature: 'email',
|
||||
parameter: 'email',
|
||||
description: 'email',
|
||||
alias: ['e', 'u']
|
||||
}, {
|
||||
signature: 'password',
|
||||
parameter: 'password',
|
||||
description: 'password',
|
||||
alias: 'p'
|
||||
}
|
||||
],
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
console.info("To login to the Resin CLI, you need your unique token, which is accesible from\nthe preferences page at " + TOKEN_URL + "\n\nAttempting to open a browser at that location...");
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return open(TOKEN_URL, function(error) {
|
||||
if (error != null) {
|
||||
console.error("Unable to open a web browser in the current environment.\nPlease visit " + TOKEN_URL + " manually.");
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
}, function(callback) {
|
||||
if (params.token != null) {
|
||||
return callback(null, params.token);
|
||||
}
|
||||
return visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback);
|
||||
}, function(token, callback) {
|
||||
return resin.auth.loginWithToken(token, done);
|
||||
return form.run([
|
||||
{
|
||||
message: 'Email:',
|
||||
name: 'email',
|
||||
type: 'input',
|
||||
validate: validation.validateEmail
|
||||
}, {
|
||||
message: 'Password:',
|
||||
name: 'password',
|
||||
type: 'password'
|
||||
}
|
||||
], done);
|
||||
], {
|
||||
override: options
|
||||
}).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) {
|
||||
if (isTwoFactorAuthPassed) {
|
||||
return;
|
||||
}
|
||||
return form.ask({
|
||||
message: 'Two factor auth challenge:',
|
||||
name: 'code',
|
||||
type: 'input'
|
||||
}).then(resin.auth.twoFactor.challenge)["catch"](function() {
|
||||
return resin.auth.logout().then(function() {
|
||||
throw new Error('Invalid two factor authentication code');
|
||||
});
|
||||
});
|
||||
}).then(resin.auth.whoami).tap(function(username) {
|
||||
console.info("Successfully logged in as: " + username);
|
||||
return events.send('user.login');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,79 +73,51 @@
|
||||
help: 'Use this command to logout from your resin.io account.o\n\nExamples:\n\n $ resin logout',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.auth.logout(done);
|
||||
return resin.auth.logout().then(function() {
|
||||
return events.send('user.logout');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.signup = {
|
||||
signature: 'signup',
|
||||
description: 'signup to resin.io',
|
||||
help: 'Use this command to signup for a resin.io account.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: me@mycompany.com\n Username: johndoe\n Password: ***********\n\n $ resin signup --email me@mycompany.com --username johndoe --password ***********\n\n $ resin whoami\n johndoe',
|
||||
options: [
|
||||
{
|
||||
signature: 'email',
|
||||
parameter: 'email',
|
||||
description: 'user email',
|
||||
alias: 'e'
|
||||
}, {
|
||||
signature: 'username',
|
||||
parameter: 'username',
|
||||
description: 'user name',
|
||||
alias: 'u'
|
||||
}, {
|
||||
signature: 'password',
|
||||
parameter: 'user password',
|
||||
description: 'user password',
|
||||
alias: 'p'
|
||||
}
|
||||
],
|
||||
help: 'Use this command to signup for a resin.io account.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: me@mycompany.com\n Username: johndoe\n Password: ***********\n\n $ resin whoami\n johndoe',
|
||||
action: function(params, options, done) {
|
||||
var hasOptionCredentials;
|
||||
hasOptionCredentials = !_.isEmpty(options);
|
||||
if (hasOptionCredentials) {
|
||||
if (options.email == null) {
|
||||
return done(new Error('Missing email'));
|
||||
return form.run([
|
||||
{
|
||||
message: 'Email:',
|
||||
name: 'email',
|
||||
type: 'input',
|
||||
validate: validation.validateEmail
|
||||
}, {
|
||||
message: 'Username:',
|
||||
name: 'username',
|
||||
type: 'input'
|
||||
}, {
|
||||
message: 'Password:',
|
||||
name: 'password',
|
||||
type: 'password',
|
||||
validate: validation.validatePassword
|
||||
}
|
||||
if (options.username == null) {
|
||||
return done(new Error('Missing username'));
|
||||
}
|
||||
if (options.password == null) {
|
||||
return done(new Error('Missing password'));
|
||||
}
|
||||
}
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (hasOptionCredentials) {
|
||||
return callback(null, options);
|
||||
}
|
||||
return visuals.widgets.register(callback);
|
||||
}, function(credentials, callback) {
|
||||
return resin.auth.register(credentials, function(error, token) {
|
||||
return callback(error, credentials);
|
||||
});
|
||||
}, function(credentials, callback) {
|
||||
return resin.auth.login(credentials, callback);
|
||||
}
|
||||
], done);
|
||||
]).then(resin.auth.register).then(resin.auth.loginWithToken).tap(function() {
|
||||
return events.send('user.signup');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.whoami = {
|
||||
signature: 'whoami',
|
||||
description: 'get current username',
|
||||
help: 'Use this command to find out the current logged in username.\n\nExamples:\n\n $ resin whoami',
|
||||
description: 'get current username and email address',
|
||||
help: 'Use this command to find out the current logged in username and email address.\n\nExamples:\n\n $ resin whoami',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.auth.whoami(function(error, username) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (username == null) {
|
||||
return done(new Error('Username not found'));
|
||||
}
|
||||
console.log(username);
|
||||
return done();
|
||||
});
|
||||
return Promise.props({
|
||||
username: resin.auth.whoami(),
|
||||
email: resin.auth.getEmail()
|
||||
}).then(function(results) {
|
||||
return console.log(visuals.table.vertical(results, ['$account information$', 'username', 'email']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -21,6 +21,20 @@
|
||||
required: 'You have to specify an application'
|
||||
}, exports.optionalApplication);
|
||||
|
||||
exports.optionalDevice = {
|
||||
signature: 'device',
|
||||
parameter: 'device',
|
||||
description: 'device name',
|
||||
alias: 'd'
|
||||
};
|
||||
|
||||
exports.booleanDevice = {
|
||||
signature: 'device',
|
||||
description: 'device name',
|
||||
boolean: true,
|
||||
alias: 'd'
|
||||
};
|
||||
|
||||
exports.network = {
|
||||
signature: 'network',
|
||||
parameter: 'network',
|
||||
|
@ -1,77 +1,109 @@
|
||||
(function() {
|
||||
var _, async, capitano, commandOptions, osAction, path, resin, tmp, vcs, visuals;
|
||||
var Promise, _, capitano, commandOptions, events, form, helpers, patterns, resin, rimraf, tmp, visuals;
|
||||
|
||||
capitano = require('capitano');
|
||||
Promise = require('bluebird');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
|
||||
path = require('path');
|
||||
|
||||
async = require('async');
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
vcs = require('resin-vcs');
|
||||
form = require('resin-cli-form');
|
||||
|
||||
tmp = require('tmp');
|
||||
events = require('resin-cli-events');
|
||||
|
||||
rimraf = Promise.promisify(require('rimraf'));
|
||||
|
||||
patterns = require('../utils/patterns');
|
||||
|
||||
helpers = require('../utils/helpers');
|
||||
|
||||
tmp = Promise.promisifyAll(require('tmp'));
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
osAction = require('./os');
|
||||
|
||||
exports.list = {
|
||||
signature: 'devices',
|
||||
description: 'list all devices',
|
||||
help: 'Use this command to list all devices that belong to you.\n\nYou can filter the devices by application by using the `--application` option.\n\nExamples:\n\n $ resin devices\n $ resin devices --application MyApp\n $ resin devices --app MyApp\n $ resin devices -a MyApp',
|
||||
options: [commandOptions.optionalApplication],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
var getFunction;
|
||||
if (options.application != null) {
|
||||
getFunction = _.partial(resin.models.device.getAllByApplication, options.application);
|
||||
} else {
|
||||
getFunction = resin.models.device.getAll;
|
||||
}
|
||||
return getFunction(function(error, devices) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
return Promise["try"](function() {
|
||||
if (options.application != null) {
|
||||
return resin.models.device.getAllByApplication(options.application);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(devices, ['id', 'name', 'device_type', 'is_online', 'application_name', 'status', 'last_seen']));
|
||||
return done();
|
||||
});
|
||||
return resin.models.device.getAll();
|
||||
}).tap(function(devices) {
|
||||
return console.log(visuals.table.horizontal(devices, ['id', 'name', 'device_type', 'is_online', 'application_name', 'status', 'last_seen']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'device <name>',
|
||||
signature: 'device <uuid>',
|
||||
description: 'list a single device',
|
||||
help: 'Use this command to show information about a single device.\n\nExamples:\n\n $ resin device MyDevice',
|
||||
help: 'Use this command to show information about a single device.\n\nExamples:\n\n $ resin device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.get(params.name, function(error, device) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
return resin.models.device.get(params.uuid).then(function(device) {
|
||||
if (device.last_seen == null) {
|
||||
device.last_seen = 'Not seen';
|
||||
}
|
||||
console.log(visuals.widgets.table.vertical(device, ['id', 'name', 'device_type', 'is_online', 'ip_address', 'application_name', 'status', 'last_seen', 'uuid', 'commit', 'supervisor_version', 'is_web_accessible', 'note']));
|
||||
return done();
|
||||
});
|
||||
console.log(visuals.table.vertical(device, ["$" + device.name + "$", 'id', 'device_type', 'is_online', 'ip_address', 'application_name', 'status', 'last_seen', 'uuid', 'commit', 'supervisor_version', 'is_web_accessible', 'note']));
|
||||
return events.send('device.open', {
|
||||
device: device.uuid
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.register = {
|
||||
signature: 'device register <application>',
|
||||
description: 'register a device',
|
||||
help: 'Use this command to register a device to an application.\n\nExamples:\n\n $ resin device register MyApp',
|
||||
permission: 'user',
|
||||
options: [
|
||||
{
|
||||
signature: 'uuid',
|
||||
description: 'custom uuid',
|
||||
parameter: 'uuid',
|
||||
alias: 'u'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.get(params.application).then(function(application) {
|
||||
return Promise["try"](function() {
|
||||
return options.uuid || resin.models.device.generateUUID();
|
||||
}).then(function(uuid) {
|
||||
console.info("Registering to " + application.app_name + ": " + uuid);
|
||||
return resin.models.device.register(application.app_name, uuid);
|
||||
});
|
||||
}).get('uuid').nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'device rm <name>',
|
||||
signature: 'device rm <uuid>',
|
||||
description: 'remove a device',
|
||||
help: 'Use this command to remove a device from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device rm MyDevice\n $ resin device rm MyDevice --yes',
|
||||
help: 'Use this command to remove a device from resin.io.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9\n $ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('device', options.yes, function(callback) {
|
||||
return resin.models.device.remove(params.name, callback);
|
||||
}, done);
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then(function() {
|
||||
return resin.models.device.remove(params.uuid);
|
||||
}).tap(function() {
|
||||
return events.send('device.delete', {
|
||||
device: params.uuid
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -81,132 +113,77 @@
|
||||
help: 'Use this command to identify a device.\n\nIn the Raspberry Pi, the ACT led is blinked several times.\n\nExamples:\n\n $ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.identify(params.uuid, done);
|
||||
return resin.models.device.identify(params.uuid).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.rename = {
|
||||
signature: 'device rename <name> [newName]',
|
||||
signature: 'device rename <uuid> [newName]',
|
||||
description: 'rename a resin device',
|
||||
help: 'Use this command to rename a device.\n\nIf you omit the name, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device rename MyDevice MyPi\n $ resin device rename MyDevice',
|
||||
help: 'Use this command to rename a device.\n\nIf you omit the name, you\'ll get asked for it interactively.\n\nExamples:\n\n $ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 MyPi\n $ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (!_.isEmpty(params.newName)) {
|
||||
return callback(null, params.newName);
|
||||
}
|
||||
return visuals.widgets.ask('How do you want to name this device?', null, callback);
|
||||
}, function(newName, callback) {
|
||||
return resin.models.device.rename(params.name, newName, callback);
|
||||
return Promise["try"](function() {
|
||||
if (!_.isEmpty(params.newName)) {
|
||||
return params.newName;
|
||||
}
|
||||
], done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.supported = {
|
||||
signature: 'devices supported',
|
||||
description: 'list all supported devices',
|
||||
help: 'Use this command to get the list of all supported devices\n\nExamples:\n\n $ resin devices supported',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.device.getSupportedDeviceTypes(function(error, devices) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
_.each(devices, _.unary(console.log));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.await = {
|
||||
signature: 'device await <name>',
|
||||
description: 'await for a device to become online',
|
||||
help: 'Use this command to await for a device to become online.\n\nThe process will exit when the device becomes online.\n\nNotice that there is no time limit for this command, so it might run forever.\n\nYou can configure the poll interval with the --interval option (defaults to 3000ms).\n\nExamples:\n\n $ resin device await MyDevice\n $ resin device await MyDevice --interval 1000',
|
||||
options: [
|
||||
{
|
||||
signature: 'interval',
|
||||
parameter: 'interval',
|
||||
description: 'poll interval',
|
||||
alias: 'i'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var poll;
|
||||
if (options.interval == null) {
|
||||
options.interval = 3000;
|
||||
}
|
||||
poll = function() {
|
||||
return resin.models.device.isOnline(params.name, function(error, isOnline) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (isOnline) {
|
||||
console.info("Device became online: " + params.name);
|
||||
return done();
|
||||
} else {
|
||||
console.info("Polling device network status: " + params.name);
|
||||
return setTimeout(poll, options.interval);
|
||||
}
|
||||
return form.ask({
|
||||
message: 'How do you want to name this device?',
|
||||
type: 'input'
|
||||
});
|
||||
};
|
||||
return poll();
|
||||
}).then(_.partial(resin.models.device.rename, params.uuid)).tap(function() {
|
||||
return events.send('device.rename', {
|
||||
device: params.uuid
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.init = {
|
||||
signature: 'device init [device]',
|
||||
signature: 'device init',
|
||||
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 by passing the `--quiet` boolean option.\n\nYou may have to unmount the device before attempting this operation.\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',
|
||||
options: [commandOptions.optionalApplication, commandOptions.network, commandOptions.wifiSsid, commandOptions.wifiKey],
|
||||
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.yes, {
|
||||
signature: 'advanced',
|
||||
description: 'enable advanced configuration',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (options.application != null) {
|
||||
return callback(null, options.application);
|
||||
}
|
||||
return vcs.getApplicationName(process.cwd(), callback);
|
||||
}, function(applicationName, callback) {
|
||||
params.name = applicationName;
|
||||
if (params.device != null) {
|
||||
return callback(null, params.device);
|
||||
}
|
||||
return visuals.patterns.selectDrive(callback);
|
||||
}, function(device, callback) {
|
||||
var message;
|
||||
params.device = device;
|
||||
message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
|
||||
return visuals.patterns.confirm(options.yes, message, callback);
|
||||
}, function(confirmed, callback) {
|
||||
if (!confirmed) {
|
||||
return done();
|
||||
}
|
||||
options.yes = confirmed;
|
||||
return tmp.file({
|
||||
prefix: 'resin-image-',
|
||||
postfix: '.img'
|
||||
}, callback);
|
||||
}, function(tmpPath, tmpFd, cleanupCallback, callback) {
|
||||
options.output = tmpPath;
|
||||
return osAction.download.action(params, options, function(error, outputFile) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null, outputFile, cleanupCallback);
|
||||
});
|
||||
}, function(outputFile, cleanupCallback, callback) {
|
||||
return capitano.run("os install " + outputFile + " " + params.device, function(error) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
cleanupCallback();
|
||||
return callback();
|
||||
});
|
||||
return Promise["try"](function() {
|
||||
if (options.application != null) {
|
||||
return options.application;
|
||||
}
|
||||
], done);
|
||||
return patterns.selectApplication();
|
||||
}).then(resin.models.application.get).then(function(application) {
|
||||
var download;
|
||||
download = function() {
|
||||
return tmp.tmpNameAsync().then(function(temporalPath) {
|
||||
return capitano.runAsync("os download " + application.device_type + " --output " + temporalPath);
|
||||
}).disposer(function(temporalPath) {
|
||||
return rimraf(temporalPath);
|
||||
});
|
||||
};
|
||||
return Promise.using(download(), function(temporalPath) {
|
||||
return capitano.runAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) {
|
||||
var configure;
|
||||
configure = "os configure " + temporalPath + " " + device.uuid;
|
||||
if (options.advanced) {
|
||||
configure += ' --advanced';
|
||||
}
|
||||
return capitano.runAsync(configure).then(function() {
|
||||
return helpers.sudo(['os', 'initialize', temporalPath, '--type', application.device_type]);
|
||||
});
|
||||
});
|
||||
}).then(function(device) {
|
||||
console.log('Done');
|
||||
return device.uuid;
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
(function() {
|
||||
var _, async, drivelist, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
async = require('async');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
drivelist = require('drivelist');
|
||||
|
||||
exports.list = {
|
||||
signature: 'drives',
|
||||
description: 'list available drives',
|
||||
help: 'Use this command to list all drives that are connected to your machine.\n\nExamples:\n\n $ resin drives',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return drivelist.list(function(error, drives) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
return async.reject(drives, drivelist.isSystem, function(removableDrives) {
|
||||
if (_.isEmpty(removableDrives)) {
|
||||
return done(new Error('No removable devices available'));
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(removableDrives, ['device', 'description', 'size']));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,20 +1,26 @@
|
||||
(function() {
|
||||
var _, commandOptions, resin, visuals;
|
||||
var Promise, _, commandOptions, events, patterns, resin, visuals;
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
Promise = require('bluebird');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
events = require('resin-cli-events');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
patterns = require('../utils/patterns');
|
||||
|
||||
exports.list = {
|
||||
signature: 'envs',
|
||||
description: 'list all environment variables',
|
||||
help: 'Use this command to list all environment variables for a particular application.\nNotice we will support per-device environment variables soon.\n\nThis command lists all custom environment variables set on the devices running\nthe application. If you want to see all environment variables, including private\nones used by resin, use the verbose option.\n\nExample:\n\n $ resin envs --application 91\n $ resin envs --application 91 --verbose',
|
||||
help: 'Use this command to list all environment variables for\na particular application or device.\n\nThis command lists all custom environment variables.\nIf you want to see all environment variables, including private\nones used by resin, use the verbose option.\n\nExample:\n\n $ resin envs --application MyApp\n $ resin envs --application MyApp --verbose\n $ resin envs --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9',
|
||||
options: [
|
||||
commandOptions.application, {
|
||||
commandOptions.optionalApplication, commandOptions.optionalDevice, {
|
||||
signature: 'verbose',
|
||||
description: 'show private environment variables',
|
||||
boolean: true,
|
||||
@ -23,58 +29,110 @@
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.environmentVariables.getAllByApplication(options.application, function(error, environmentVariables) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
return Promise["try"](function() {
|
||||
if (options.application != null) {
|
||||
return resin.models.environmentVariables.getAllByApplication(options.application);
|
||||
} else if (options.device != null) {
|
||||
return resin.models.environmentVariables.device.getAll(options.device);
|
||||
} else {
|
||||
throw new Error('You must specify an application or device');
|
||||
}
|
||||
}).tap(function(environmentVariables) {
|
||||
var isSystemVariable;
|
||||
if (_.isEmpty(environmentVariables)) {
|
||||
throw new Error('No environment variables found');
|
||||
}
|
||||
if (!options.verbose) {
|
||||
environmentVariables = _.reject(environmentVariables, resin.models.environmentVariables.isSystemVariable);
|
||||
isSystemVariable = resin.models.environmentVariables.isSystemVariable;
|
||||
environmentVariables = _.reject(environmentVariables, isSystemVariable);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(environmentVariables, ['id', 'name', 'value']));
|
||||
return done();
|
||||
});
|
||||
return console.log(visuals.table.horizontal(environmentVariables, ['id', 'name', 'value']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'env rm <id>',
|
||||
description: 'remove an environment variable',
|
||||
help: 'Use this command to remove an environment variable from an application.\n\nDon\'t remove resin specific variables, as things might not work as expected.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin env rm 215\n $ resin env rm 215 --yes',
|
||||
options: [commandOptions.yes],
|
||||
help: 'Use this command to remove an environment variable from an application.\n\nDon\'t remove resin specific variables, as things might not work as expected.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nIf you want to eliminate a device environment variable, pass the `--device` boolean option.\n\nExamples:\n\n $ resin env rm 215\n $ resin env rm 215 --yes\n $ resin env rm 215 --device',
|
||||
options: [commandOptions.yes, commandOptions.booleanDevice],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('environment variable', options.yes, function(callback) {
|
||||
return resin.models.environmentVariables.remove(params.id, callback);
|
||||
}, done);
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then(function() {
|
||||
if (options.device) {
|
||||
resin.models.environmentVariables.device.remove(params.id);
|
||||
return events.send('deviceEnvironmentVariable.delete', {
|
||||
id: params.id
|
||||
});
|
||||
} else {
|
||||
resin.models.environmentVariables.remove(params.id);
|
||||
return events.send('environmentVariable.delete', {
|
||||
id: params.id
|
||||
});
|
||||
}
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.add = {
|
||||
signature: 'env add <key> [value]',
|
||||
description: 'add an environment variable',
|
||||
help: 'Use this command to add an enviroment variable to an application.\n\nYou need to pass the `--application` option.\n\nIf value is omitted, the tool will attempt to use the variable\'s value\nas defined in your host machine.\n\nIf the value is grabbed from the environment, a warning message will be printed.\nUse `--quiet` to remove it.\n\nExamples:\n\n $ resin env add EDITOR vim -a 91\n $ resin env add TERM -a 91',
|
||||
options: [commandOptions.application],
|
||||
help: 'Use this command to add an enviroment variable to an application.\n\nIf value is omitted, the tool will attempt to use the variable\'s value\nas defined in your host machine.\n\nUse the `--device` option if you want to assign the environment variable\nto a specific device.\n\nIf the value is grabbed from the environment, a warning message will be printed.\nUse `--quiet` to remove it.\n\nExamples:\n\n $ resin env add EDITOR vim --application MyApp\n $ resin env add TERM --application MyApp\n $ resin env add EDITOR vim --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9',
|
||||
options: [commandOptions.optionalApplication, commandOptions.optionalDevice],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
if (params.value == null) {
|
||||
params.value = process.env[params.key];
|
||||
return Promise["try"](function() {
|
||||
if (params.value == null) {
|
||||
return done(new Error("Environment value not found for key: " + params.key));
|
||||
} else {
|
||||
console.info("Warning: using " + params.key + "=" + params.value + " from host environment");
|
||||
params.value = process.env[params.key];
|
||||
if (params.value == null) {
|
||||
throw new Error("Environment value not found for key: " + params.key);
|
||||
} else {
|
||||
console.info("Warning: using " + params.key + "=" + params.value + " from host environment");
|
||||
}
|
||||
}
|
||||
}
|
||||
return resin.models.environmentVariables.create(options.application, params.key, params.value, done);
|
||||
if (options.application != null) {
|
||||
return resin.models.environmentVariables.create(options.application, params.key, params.value).then(function() {
|
||||
return resin.models.application.get(options.application).then(function(application) {
|
||||
return events.send('environmentVariable.create', {
|
||||
application: application.id
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (options.device != null) {
|
||||
return resin.models.environmentVariables.device.create(options.device, params.key, params.value).then(function() {
|
||||
return events.send('deviceEnvironmentVariable.create', {
|
||||
device: options.device
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw new Error('You must specify an application or device');
|
||||
}
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.rename = {
|
||||
signature: 'env rename <id> <value>',
|
||||
description: 'rename an environment variable',
|
||||
help: 'Use this command to rename an enviroment variable from an application.\n\nExamples:\n\n $ resin env rename 376 emacs',
|
||||
help: 'Use this command to rename an enviroment variable from an application.\n\nPass the `--device` boolean option if you want to rename a device environment variable.\n\nExamples:\n\n $ resin env rename 376 emacs\n $ resin env rename 376 emacs --device',
|
||||
permission: 'user',
|
||||
options: [commandOptions.booleanDevice],
|
||||
action: function(params, options, done) {
|
||||
return resin.models.environmentVariables.update(params.id, params.value, done);
|
||||
return Promise["try"](function() {
|
||||
if (options.device) {
|
||||
return resin.models.environmentVariables.device.update(params.id, params.value).then(function() {
|
||||
return events.send('deviceEnvironmentVariable.edit', {
|
||||
id: params.id
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return resin.models.environmentVariables.update(params.id, params.value).then(function() {
|
||||
return events.send('environmentVariable.edit', {
|
||||
id: params.id
|
||||
});
|
||||
});
|
||||
}
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
(function() {
|
||||
var _, async, examplesData, fs, mkdirp, path, resin, vcs, visuals;
|
||||
|
||||
mkdirp = require('mkdirp');
|
||||
|
||||
async = require('async');
|
||||
|
||||
fs = require('fs');
|
||||
|
||||
path = require('path');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
vcs = require('resin-vcs');
|
||||
|
||||
examplesData = require('../data/examples.json');
|
||||
|
||||
exports.list = {
|
||||
signature: 'examples',
|
||||
description: 'list all example applications',
|
||||
help: 'Use this command to list available example applications from resin.io\n\nExample:\n\n $ resin examples',
|
||||
permission: 'user',
|
||||
action: function() {
|
||||
examplesData = _.map(examplesData, function(example, index) {
|
||||
example.id = index + 1;
|
||||
return example;
|
||||
});
|
||||
examplesData = _.map(examplesData, function(example) {
|
||||
if (example.author == null) {
|
||||
example.author = 'Unknown';
|
||||
}
|
||||
return example;
|
||||
});
|
||||
return console.log(visuals.widgets.table.horizontal(examplesData, ['id', 'display_name', 'repository', 'author']));
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
signature: 'example <id>',
|
||||
description: 'list a single example application',
|
||||
help: 'Use this command to show information of a single example application\n\nExample:\n\n $ resin example 3',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var example, id;
|
||||
id = params.id - 1;
|
||||
example = examplesData[id];
|
||||
if (example == null) {
|
||||
return done(new Error("Unknown example: " + id));
|
||||
}
|
||||
example.id = id;
|
||||
if (example.author == null) {
|
||||
example.author = 'Unknown';
|
||||
}
|
||||
console.log(visuals.widgets.table.vertical(example, ['id', 'display_name', 'description', 'author', 'repository']));
|
||||
return done();
|
||||
}
|
||||
};
|
||||
|
||||
exports.clone = {
|
||||
signature: 'example clone <id>',
|
||||
description: 'clone an example application',
|
||||
help: 'Use this command to clone an example application to the current directory\n\nThis command outputs information about the cloning process.\nUse `--quiet` to remove that output.\n\nExample:\n\n $ resin example clone 3',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
var currentDirectory, destination, example;
|
||||
example = examplesData[params.id - 1];
|
||||
if (example == null) {
|
||||
return done(new Error("Unknown example: " + id));
|
||||
}
|
||||
currentDirectory = process.cwd();
|
||||
destination = path.join(currentDirectory, example.name);
|
||||
return mkdirp(destination, function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Cloning " + example.display_name + " to " + destination);
|
||||
vcs.clone(example.repository, destination, done);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,125 +1,80 @@
|
||||
(function() {
|
||||
var PADDING_INITIAL, PADDING_MIDDLE, _, addAlias, addOptionPrefix, buildHelpString, buildOptionSignatureHelp, capitano, command, general, getCommandHelp, getFieldMaxLength, getOptionHelp, getOptionsParsedSignatures, resin;
|
||||
var _, capitano, columnify, command, general, indent, parse, print;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
PADDING_INITIAL = ' ';
|
||||
columnify = require('columnify');
|
||||
|
||||
PADDING_MIDDLE = '\t';
|
||||
|
||||
getFieldMaxLength = function(array, field) {
|
||||
return _.max(_.map(array, function(item) {
|
||||
return item[field].toString().length;
|
||||
}));
|
||||
};
|
||||
|
||||
buildHelpString = function(firstColumn, secondColumn) {
|
||||
var result;
|
||||
result = "" + PADDING_INITIAL + firstColumn;
|
||||
result += "" + PADDING_MIDDLE + secondColumn;
|
||||
return result;
|
||||
};
|
||||
|
||||
addOptionPrefix = function(option) {
|
||||
if (option.length <= 0) {
|
||||
return;
|
||||
}
|
||||
if (option.length === 1) {
|
||||
return "-" + option;
|
||||
} else {
|
||||
return "--" + option;
|
||||
}
|
||||
};
|
||||
|
||||
addAlias = function(alias) {
|
||||
return ", " + (addOptionPrefix(alias));
|
||||
};
|
||||
|
||||
buildOptionSignatureHelp = function(option) {
|
||||
var alias, i, len, ref, result;
|
||||
result = addOptionPrefix(option.signature.toString());
|
||||
if (_.isString(option.alias)) {
|
||||
result += addAlias(option.alias);
|
||||
} else if (_.isArray(option.alias)) {
|
||||
ref = option.alias;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
alias = ref[i];
|
||||
result += addAlias(alias);
|
||||
parse = function(object) {
|
||||
return _.object(_.map(object, function(item) {
|
||||
var signature;
|
||||
if (item.alias != null) {
|
||||
signature = item.toString();
|
||||
} else {
|
||||
signature = item.signature.toString();
|
||||
}
|
||||
}
|
||||
if (option.parameter != null) {
|
||||
result += " <" + option.parameter + ">";
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
getCommandHelp = function(command) {
|
||||
var commandSignature, maxSignatureLength;
|
||||
maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature');
|
||||
commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ');
|
||||
return buildHelpString(commandSignature, command.description);
|
||||
};
|
||||
|
||||
getOptionsParsedSignatures = function(optionsHelp) {
|
||||
var maxLength;
|
||||
maxLength = _.max(_.map(optionsHelp, function(signature) {
|
||||
return signature.length;
|
||||
return [signature, item.description];
|
||||
}));
|
||||
return _.map(optionsHelp, function(signature) {
|
||||
return _.str.rpad(signature, maxLength, ' ');
|
||||
};
|
||||
|
||||
indent = function(text) {
|
||||
text = _.map(_.str.lines(text), function(line) {
|
||||
return ' ' + line;
|
||||
});
|
||||
return text.join('\n');
|
||||
};
|
||||
|
||||
getOptionHelp = function(option, maxLength) {
|
||||
var result;
|
||||
result = PADDING_INITIAL;
|
||||
result += _.str.rpad(option.signature, maxLength, ' ');
|
||||
result += PADDING_MIDDLE;
|
||||
result += option.description;
|
||||
return result;
|
||||
print = function(data) {
|
||||
return console.log(indent(columnify(data, {
|
||||
showHeaders: false,
|
||||
minWidth: 35
|
||||
})));
|
||||
};
|
||||
|
||||
general = function() {
|
||||
var command, i, j, len, len1, option, optionSignatureMaxLength, options, ref;
|
||||
general = function(params, options, done) {
|
||||
var commands, groupedCommands;
|
||||
console.log('Usage: resin [COMMAND] [OPTIONS]\n');
|
||||
console.log('Commands:\n');
|
||||
ref = capitano.state.commands;
|
||||
for (i = 0, len = ref.length; i < len; i++) {
|
||||
command = ref[i];
|
||||
if (command.isWildcard()) {
|
||||
continue;
|
||||
}
|
||||
console.log(getCommandHelp(command));
|
||||
}
|
||||
console.log('\nGlobal Options:\n');
|
||||
options = _.map(capitano.state.globalOptions, function(option) {
|
||||
option.signature = buildOptionSignatureHelp(option);
|
||||
return option;
|
||||
console.log('Primary commands:\n');
|
||||
commands = _.reject(capitano.state.commands, function(command) {
|
||||
return command.isWildcard();
|
||||
});
|
||||
optionSignatureMaxLength = _.max(_.map(options, function(option) {
|
||||
return option.signature.length;
|
||||
}));
|
||||
for (j = 0, len1 = options.length; j < len1; j++) {
|
||||
option = options[j];
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength));
|
||||
groupedCommands = _.groupBy(commands, function(command) {
|
||||
if (command.plugin) {
|
||||
return 'plugins';
|
||||
} else if (command.primary) {
|
||||
return 'primary';
|
||||
}
|
||||
return 'secondary';
|
||||
});
|
||||
print(parse(groupedCommands.primary));
|
||||
if (options.verbose) {
|
||||
if (!_.isEmpty(groupedCommands.plugins)) {
|
||||
console.log('\nInstalled plugins:\n');
|
||||
print(parse(groupedCommands.plugins));
|
||||
}
|
||||
console.log('\nAdditional commands:\n');
|
||||
print(parse(groupedCommands.secondary));
|
||||
} else {
|
||||
console.log('\nRun `resin help --verbose` to list additional commands');
|
||||
}
|
||||
return console.log();
|
||||
if (!_.isEmpty(capitano.state.globalOptions)) {
|
||||
console.log('\nGlobal Options:\n');
|
||||
print(parse(capitano.state.globalOptions));
|
||||
}
|
||||
return done();
|
||||
};
|
||||
|
||||
command = function(params, options, done) {
|
||||
return capitano.state.getMatchCommand(params.command, function(error, command) {
|
||||
var i, len, option, optionSignatureMaxLength;
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if ((command == null) || command.isWildcard()) {
|
||||
return capitano.defaults.actions.commandNotFound(params.command);
|
||||
return done(new Error("Command not found: " + params.command));
|
||||
}
|
||||
console.log("Usage: " + command.signature);
|
||||
if (command.help != null) {
|
||||
@ -129,18 +84,7 @@
|
||||
}
|
||||
if (!_.isEmpty(command.options)) {
|
||||
console.log('\nOptions:\n');
|
||||
options = _.map(command.options, function(option) {
|
||||
option.signature = buildOptionSignatureHelp(option);
|
||||
return option;
|
||||
});
|
||||
optionSignatureMaxLength = _.max(_.map(options, function(option) {
|
||||
return option.signature.toString().length;
|
||||
}));
|
||||
for (i = 0, len = options.length; i < len; i++) {
|
||||
option = options[i];
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength));
|
||||
}
|
||||
console.log();
|
||||
print(parse(command.options));
|
||||
}
|
||||
return done();
|
||||
});
|
||||
@ -150,6 +94,15 @@
|
||||
signature: 'help [command...]',
|
||||
description: 'show help',
|
||||
help: 'Get detailed help for an specific command.\n\nExamples:\n\n $ resin help apps\n $ resin help os download',
|
||||
primary: true,
|
||||
options: [
|
||||
{
|
||||
signature: 'verbose',
|
||||
description: 'show additional commands',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
if (params.command != null) {
|
||||
return command(params, options, done);
|
||||
|
@ -1,20 +1,16 @@
|
||||
(function() {
|
||||
module.exports = {
|
||||
wizard: require('./wizard'),
|
||||
app: require('./app'),
|
||||
info: require('./info'),
|
||||
auth: require('./auth'),
|
||||
drive: require('./drive'),
|
||||
device: require('./device'),
|
||||
env: require('./environment-variables'),
|
||||
keys: require('./keys'),
|
||||
logs: require('./logs'),
|
||||
notes: require('./notes'),
|
||||
preferences: require('./preferences'),
|
||||
os: require('./os'),
|
||||
help: require('./help'),
|
||||
examples: require('./examples'),
|
||||
plugin: require('./plugin'),
|
||||
update: require('./update')
|
||||
os: require('./os')
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
|
@ -1,54 +1,46 @@
|
||||
(function() {
|
||||
var SSH_KEY_WIDTH, _, async, capitano, commandOptions, fs, resin, visuals;
|
||||
var Promise, _, capitano, commandOptions, events, fs, patterns, resin, visuals;
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
fs = Promise.promisifyAll(require('fs'));
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
async = require('async');
|
||||
|
||||
fs = require('fs');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
events = require('resin-cli-events');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
patterns = require('../utils/patterns');
|
||||
|
||||
exports.list = {
|
||||
signature: 'keys',
|
||||
description: 'list all ssh keys',
|
||||
help: 'Use this command to list all your SSH keys.\n\nExamples:\n\n $ resin keys',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.key.getAll(function(error, keys) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(keys, ['id', 'title']));
|
||||
return done();
|
||||
});
|
||||
return resin.models.key.getAll().then(function(keys) {
|
||||
return console.log(visuals.table.horizontal(keys, ['id', 'title']));
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
SSH_KEY_WIDTH = 43;
|
||||
|
||||
exports.info = {
|
||||
signature: 'key <id>',
|
||||
description: 'list a single ssh key',
|
||||
help: 'Use this command to show information about a single SSH key.\n\nExamples:\n\n $ resin key 17',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.key.get(params.id, function(error, key) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
key.public_key = '\n' + visuals.helpers.chop(key.public_key, SSH_KEY_WIDTH);
|
||||
console.log(visuals.widgets.table.vertical(key, ['id', 'title', 'public_key']));
|
||||
return done();
|
||||
});
|
||||
return resin.models.key.get(params.id).then(function(key) {
|
||||
console.log(visuals.table.vertical(key, ['id', 'title']));
|
||||
return console.log('\n' + key.public_key);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -59,9 +51,13 @@
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('key', options.yes, function(callback) {
|
||||
return resin.models.key.remove(params.id, callback);
|
||||
}, done);
|
||||
return patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then(function() {
|
||||
return resin.models.key.remove(params.id);
|
||||
}).tap(function() {
|
||||
return events.send('publicKey.delete', {
|
||||
id: params.id
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
@ -71,21 +67,20 @@
|
||||
help: 'Use this command to associate a new SSH key with your account.\n\nIf `path` is omitted, the command will attempt\nto read the SSH key from stdin.\n\nExamples:\n\n $ resin key add Main ~/.ssh/id_rsa.pub\n $ cat ~/.ssh/id_rsa.pub | resin key add Main',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (params.path != null) {
|
||||
return fs.readFile(params.path, {
|
||||
encoding: 'utf8'
|
||||
}, callback);
|
||||
} else {
|
||||
return capitano.utils.getStdin(function(data) {
|
||||
return callback(null, data);
|
||||
});
|
||||
}
|
||||
}, function(key, callback) {
|
||||
return resin.models.key.create(params.name, key, callback);
|
||||
return Promise["try"](function() {
|
||||
if (params.path != null) {
|
||||
return fs.readFileAsync(params.path, {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
], done);
|
||||
return Promise.fromNode(function(callback) {
|
||||
return capitano.utils.getStdin(function(data) {
|
||||
return callback(null, data);
|
||||
});
|
||||
});
|
||||
}).then(_.partial(resin.models.key.create, params.name)).tap(function() {
|
||||
return events.send('publicKey.create');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,23 +1,16 @@
|
||||
(function() {
|
||||
var LOGS_HISTORY_COUNT, _, resin;
|
||||
var _, resin;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
LOGS_HISTORY_COUNT = 200;
|
||||
|
||||
exports.logs = {
|
||||
module.exports = {
|
||||
signature: 'logs <uuid>',
|
||||
description: 'show device logs',
|
||||
help: 'Use this command to show logs for a specific device.\n\nBy default, the command prints all log messages and exit.\n\nTo limit the output to the n last lines, use the `--num` option along with a number.\nThis is similar to doing `resin logs <uuid> | tail -n X`.\n\nTo continuously stream output, and see new logs in real time, use the `--tail` option.\n\nNote that for now you need to provide the whole UUID for this command to work correctly.\n\nThis is due to some technical limitations that we plan to address soon.\n\nExamples:\n\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail',
|
||||
help: 'Use this command to show logs for a specific device.\n\nBy default, the command prints all log messages and exit.\n\nTo continuously stream output, and see new logs in real time, use the `--tail` option.\n\nNote that for now you need to provide the whole UUID for this command to work correctly.\n\nThis is due to some technical limitations that we plan to address soon.\n\nExamples:\n\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828\n $ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail',
|
||||
options: [
|
||||
{
|
||||
signature: 'num',
|
||||
parameter: 'num',
|
||||
description: 'number of lines to display',
|
||||
alias: 'n'
|
||||
}, {
|
||||
signature: 'tail',
|
||||
description: 'continuously stream output',
|
||||
boolean: true,
|
||||
@ -25,23 +18,25 @@
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return resin.logs.subscribe(params.uuid, {
|
||||
history: options.num || LOGS_HISTORY_COUNT,
|
||||
tail: options.tail
|
||||
}, function(error, message) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (_.isArray(message)) {
|
||||
_.each(message, function(line) {
|
||||
var promise;
|
||||
promise = resin.logs.history(params.uuid).each(function(line) {
|
||||
return console.log(line.message);
|
||||
});
|
||||
if (!options.tail) {
|
||||
return promise["catch"](done)["finally"](function() {
|
||||
return process.exit(0);
|
||||
});
|
||||
}
|
||||
return promise.then(function() {
|
||||
return resin.logs.subscribe(params.uuid).then(function(logs) {
|
||||
logs.on('line', function(line) {
|
||||
return console.log(line.message);
|
||||
});
|
||||
} else {
|
||||
console.log(message.message);
|
||||
}
|
||||
return done();
|
||||
});
|
||||
return logs.on('error', done);
|
||||
});
|
||||
})["catch"](done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,31 +1,33 @@
|
||||
(function() {
|
||||
var _, async, resin;
|
||||
var Promise, _, resin;
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
async = require('async');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
exports.set = {
|
||||
signature: 'note <|note>',
|
||||
description: 'set a device note',
|
||||
help: 'Use this command to set or update a device note.\n\nIf note command isn\'t passed, the tool attempts to read from `stdin`.\n\nTo view the notes, use $ resin device <name>.\n\nExamples:\n\n $ resin note "My useful note" --device MyDevice\n $ cat note.txt | resin note --device MyDevice',
|
||||
help: 'Use this command to set or update a device note.\n\nIf note command isn\'t passed, the tool attempts to read from `stdin`.\n\nTo view the notes, use $ resin device <uuid>.\n\nExamples:\n\n $ resin note "My useful note" --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9\n $ cat note.txt | resin note --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9',
|
||||
options: [
|
||||
{
|
||||
signature: 'device',
|
||||
parameter: 'device',
|
||||
description: 'device name',
|
||||
description: 'device uuid',
|
||||
alias: ['d', 'dev'],
|
||||
required: 'You have to specify a device'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
if (_.isEmpty(params.note)) {
|
||||
return done(new Error('Missing note content'));
|
||||
}
|
||||
return resin.models.device.note(options.device, params.note, done);
|
||||
return Promise["try"](function() {
|
||||
if (_.isEmpty(params.note)) {
|
||||
throw new Error('Missing note content');
|
||||
}
|
||||
return resin.models.device.note(options.device, params.note);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,156 +1,176 @@
|
||||
(function() {
|
||||
var _, async, capitano, commandOptions, elevate, image, mkdirp, npm, os, packageJSON, path, resin, visuals;
|
||||
var Promise, _, commandOptions, form, fs, helpers, init, manager, patterns, resin, rindle, stepHandler, umount, unzip, visuals;
|
||||
|
||||
capitano = require('capitano');
|
||||
fs = require('fs');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
_ = require('lodash');
|
||||
|
||||
os = require('os');
|
||||
Promise = require('bluebird');
|
||||
|
||||
async = require('async');
|
||||
umount = Promise.promisifyAll(require('umount'));
|
||||
|
||||
path = require('path');
|
||||
unzip = require('unzip2');
|
||||
|
||||
mkdirp = require('mkdirp');
|
||||
rindle = require('rindle');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
image = require('resin-image');
|
||||
manager = require('resin-image-manager');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
form = require('resin-cli-form');
|
||||
|
||||
init = require('resin-device-init');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
npm = require('../npm');
|
||||
helpers = require('../utils/helpers');
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
elevate = require('../elevate');
|
||||
patterns = require('../utils/patterns');
|
||||
|
||||
exports.download = {
|
||||
signature: 'os download <name>',
|
||||
description: 'download device OS',
|
||||
help: 'Use this command to download the device OS configured to a specific network.\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\nAlternatively, you can omit all kind of network configuration options to configure interactively.\n\nYou have to specify an output location with the `--output` option.\n\nExamples:\n\n $ resin os download MyApp --output ~/MyResinOS.zip\n $ resin os download MyApp --network ethernet --output ~/MyResinOS.zip\n $ resin os download MyApp --network wifi --ssid MyNetwork --key secreykey123 --output ~/MyResinOS.zip\n $ resin os download MyApp --network ethernet --output ~/MyResinOS.zip',
|
||||
signature: 'os download <type>',
|
||||
description: 'download an unconfigured os image',
|
||||
help: 'Use this command to download an unconfigured os image for a certain device type.\n\nExamples:\n\n $ resin os download parallella -o ../foo/bar/parallella.img',
|
||||
permission: 'user',
|
||||
options: [
|
||||
commandOptions.network, commandOptions.wifiSsid, commandOptions.wifiKey, {
|
||||
{
|
||||
signature: 'output',
|
||||
description: 'output path',
|
||||
parameter: 'output',
|
||||
description: 'output file',
|
||||
alias: 'o',
|
||||
required: 'You need to specify an output file'
|
||||
required: 'You have to specify an output location'
|
||||
}
|
||||
],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return resin.models.application.get(params.name, function(error, application) {
|
||||
var osParams;
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
osParams = {
|
||||
network: options.network,
|
||||
wifiSsid: options.ssid,
|
||||
wifiKey: options.key,
|
||||
appId: application.id
|
||||
};
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
if (osParams.network != null) {
|
||||
return callback();
|
||||
}
|
||||
return visuals.patterns.selectNetworkParameters(function(error, parameters) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
_.extend(osParams, parameters);
|
||||
return callback();
|
||||
});
|
||||
}, function(callback) {
|
||||
return mkdirp(path.dirname(options.output), _.unary(callback));
|
||||
}, function(callback) {
|
||||
var bar, spinner;
|
||||
console.info("Destination file: " + options.output + "\n");
|
||||
bar = new visuals.widgets.Progress('Downloading Device OS');
|
||||
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)');
|
||||
return resin.models.os.download(osParams, options.output, function(error) {
|
||||
spinner.stop();
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
}, function(state) {
|
||||
if (state != null) {
|
||||
return bar.update(state);
|
||||
} else {
|
||||
return spinner.start();
|
||||
}
|
||||
});
|
||||
console.info("Getting device operating system for " + params.type);
|
||||
return manager.get(params.type).then(function(stream) {
|
||||
var bar, output, 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();
|
||||
}
|
||||
], function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("\nFinished downloading " + options.output);
|
||||
return done(null, options.output);
|
||||
});
|
||||
});
|
||||
stream.on('end', function() {
|
||||
return spinner.stop();
|
||||
});
|
||||
if (stream.mime === 'application/zip') {
|
||||
output = unzip.Extract({
|
||||
path: options.output
|
||||
});
|
||||
} else {
|
||||
output = fs.createWriteStream(options.output);
|
||||
}
|
||||
return rindle.wait(stream.pipe(output))["return"](options.output);
|
||||
}).tap(function(output) {
|
||||
return console.info("The image was downloaded to " + output);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.install = {
|
||||
signature: 'os install <image> [device]',
|
||||
description: 'write an operating system image to a device',
|
||||
help: 'Use this command to write an operating system image to a device.\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 by passing the `--quiet` boolean option.\n\nExamples:\n\n $ resin os install rpi.iso /dev/disk2',
|
||||
options: [commandOptions.yes],
|
||||
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 rindle.wait(step);
|
||||
};
|
||||
|
||||
exports.configure = {
|
||||
signature: 'os configure <image> <uuid>',
|
||||
description: 'configure an os image',
|
||||
help: 'Use this command to configure a previously download operating system image with a device.\n\nExamples:\n\n $ resin os configure ../path/rpi.img 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9',
|
||||
permission: 'user',
|
||||
options: [
|
||||
{
|
||||
signature: 'advanced',
|
||||
description: 'show advanced commands',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
return npm.isUpdated(packageJSON.name, packageJSON.version, callback);
|
||||
}, function(isUpdated, callback) {
|
||||
if (isUpdated) {
|
||||
return callback();
|
||||
}
|
||||
console.info('Resin CLI is outdated.\n\nIn order to avoid device compatibility issues, this command\nrequires that you have the Resin CLI updated.\n\nUpdating now...');
|
||||
return capitano.run('update', _.unary(callback));
|
||||
}, function(callback) {
|
||||
if (params.device != null) {
|
||||
return callback(null, params.device);
|
||||
}
|
||||
return visuals.patterns.selectDrive(function(error, device) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
if (device == null) {
|
||||
return callback(new Error('No removable devices available'));
|
||||
}
|
||||
return callback(null, device);
|
||||
console.info('Configuring operating system image');
|
||||
return resin.models.device.get(params.uuid).get('device_type').then(resin.models.device.getManifestBySlug).get('options').then(function(questions) {
|
||||
var advancedGroup, override;
|
||||
if (!options.advanced) {
|
||||
advancedGroup = _.findWhere(questions, {
|
||||
name: 'advanced',
|
||||
isGroup: true
|
||||
});
|
||||
}, function(device, callback) {
|
||||
var message;
|
||||
params.device = device;
|
||||
message = "This will completely erase " + params.device + ". Are you sure you want to continue?";
|
||||
return visuals.patterns.confirm(options.yes, message, callback);
|
||||
}, function(confirmed, callback) {
|
||||
var bar;
|
||||
if (!confirmed) {
|
||||
return done();
|
||||
if (advancedGroup != null) {
|
||||
override = helpers.getGroupDefaults(advancedGroup);
|
||||
}
|
||||
bar = new visuals.widgets.Progress('Writing Device OS');
|
||||
params.progress = _.bind(bar.update, bar);
|
||||
return image.write(params, callback);
|
||||
}
|
||||
], function(error) {
|
||||
var resinWritePath;
|
||||
if (error == null) {
|
||||
return done();
|
||||
return form.run(questions, {
|
||||
override: override
|
||||
});
|
||||
}).then(function(answers) {
|
||||
return init.configure(params.image, params.uuid, answers).then(stepHandler);
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
exports.initialize = {
|
||||
signature: 'os initialize <image>',
|
||||
description: 'initialize an os image',
|
||||
help: 'Use this command to initialize a previously configured operating system image.\n\nExamples:\n\n $ resin os initialize ../path/rpi.img --type \'raspberry-pi\'',
|
||||
permission: 'user',
|
||||
options: [
|
||||
commandOptions.yes, {
|
||||
signature: 'type',
|
||||
description: 'device type',
|
||||
parameter: 'type',
|
||||
alias: 't',
|
||||
required: 'You have to specify a device type'
|
||||
}, {
|
||||
signature: 'drive',
|
||||
description: 'drive',
|
||||
parameter: 'drive',
|
||||
alias: 'd'
|
||||
}
|
||||
],
|
||||
root: true,
|
||||
action: function(params, options, done) {
|
||||
console.info('Initializing device');
|
||||
return resin.models.device.getManifestBySlug(options.type).then(function(manifest) {
|
||||
var ref;
|
||||
return (ref = manifest.initialization) != null ? ref.options : void 0;
|
||||
}).then(function(questions) {
|
||||
return form.run(questions, {
|
||||
override: {
|
||||
drive: options.drive
|
||||
}
|
||||
});
|
||||
}).tap(function(answers) {
|
||||
var message;
|
||||
if (answers.drive == null) {
|
||||
return;
|
||||
}
|
||||
if (elevate.shouldElevate(error) && !options.fromScript) {
|
||||
resinWritePath = "\"" + (path.join(__dirname, '..', '..', 'bin', 'resin-write')) + "\"";
|
||||
return elevate.run("\"" + process.argv[0] + "\" " + resinWritePath + " \"" + params.image + "\" \"" + params.device + "\"");
|
||||
} else {
|
||||
return done(error);
|
||||
message = "This will erase " + answers.drive + ". Are you sure?";
|
||||
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umount.umountAsync);
|
||||
}).tap(function(answers) {
|
||||
return init.initialize(params.image, options.type, answers).then(stepHandler);
|
||||
}).then(function(answers) {
|
||||
if (answers.drive == null) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return umount.umountAsync(answers.drive).tap(function() {
|
||||
return console.info("You can safely remove " + answers.drive + " now");
|
||||
});
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,83 +0,0 @@
|
||||
(function() {
|
||||
var _, commandOptions, plugins, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
commandOptions = require('./command-options');
|
||||
|
||||
plugins = require('../plugins');
|
||||
|
||||
exports.list = {
|
||||
signature: 'plugins',
|
||||
description: 'list all plugins',
|
||||
help: 'Use this command to list all the installed resin plugins.\n\nExamples:\n\n $ resin plugins',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return plugins.list(function(error, resinPlugins) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (_.isEmpty(resinPlugins)) {
|
||||
console.log('You don\'t have any plugins yet');
|
||||
return done();
|
||||
}
|
||||
console.log(visuals.widgets.table.horizontal(resinPlugins, ['name', 'version', 'description', 'license']));
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.install = {
|
||||
signature: 'plugin install <name>',
|
||||
description: 'install a plugin',
|
||||
help: 'Use this command to install a resin plugin\n\nUse `--quiet` to prevent information logging.\n\nExamples:\n\n $ resin plugin install hello',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return plugins.install(params.name, function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Plugin installed: " + params.name);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.update = {
|
||||
signature: 'plugin update <name>',
|
||||
description: 'update a plugin',
|
||||
help: 'Use this command to update a resin plugin\n\nUse `--quiet` to prevent information logging.\n\nExamples:\n\n $ resin plugin update hello',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return plugins.update(params.name, function(error, version) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Plugin updated: " + params.name + "@" + version);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.remove = {
|
||||
signature: 'plugin rm <name>',
|
||||
description: 'remove a plugin',
|
||||
help: 'Use this command to remove a resin.io plugin.\n\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin plugin rm hello\n $ resin plugin rm hello --yes',
|
||||
options: [commandOptions.yes],
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
return visuals.patterns.remove('plugin', options.yes, function(callback) {
|
||||
return plugins.remove(params.name, callback);
|
||||
}, function(error) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
console.info("Plugin removed: " + params.name);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,22 +0,0 @@
|
||||
(function() {
|
||||
var open, settings, url;
|
||||
|
||||
open = require('open');
|
||||
|
||||
url = require('url');
|
||||
|
||||
settings = require('resin-settings-client');
|
||||
|
||||
exports.preferences = {
|
||||
signature: 'preferences',
|
||||
description: 'open preferences form',
|
||||
help: 'Use this command to open the preferences form.\n\nIn the future, we will allow changing all preferences directly from the terminal.\nFor now, we open your default web browser and point it to the web based preferences form.\n\nExamples:\n\n $ resin preferences',
|
||||
permission: 'user',
|
||||
action: function() {
|
||||
var absUrl;
|
||||
absUrl = url.resolve(settings.get('remoteUrl'), '/preferences');
|
||||
return open(absUrl);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,51 +0,0 @@
|
||||
(function() {
|
||||
var _, child_process, npm, packageJSON, president;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
child_process = require('child_process');
|
||||
|
||||
president = require('president');
|
||||
|
||||
npm = require('../npm');
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
exports.update = {
|
||||
signature: 'update',
|
||||
description: 'update the resin cli',
|
||||
help: 'Use this command to update the Resin CLI\n\nThis command outputs information about the update process.\nUse `--quiet` to remove that output.\n\nThe Resin CLI checks for updates once per day.\n\nMajor updates require a manual update with this update command,\nwhile minor updates are applied automatically.\n\nExamples:\n\n $ resin update',
|
||||
action: function(params, options, done) {
|
||||
return npm.isUpdated(packageJSON.name, packageJSON.version, function(error, isUpdated) {
|
||||
var command, onUpdate;
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (isUpdated) {
|
||||
return done(new Error('You\'re already running the latest version.'));
|
||||
}
|
||||
onUpdate = function(error, stdout, stderr) {
|
||||
if (error != null) {
|
||||
return done(error);
|
||||
}
|
||||
if (!_.isEmpty(stderr)) {
|
||||
return done(new Error(stderr));
|
||||
}
|
||||
console.info("Upgraded " + packageJSON.name + ".");
|
||||
return done();
|
||||
};
|
||||
command = "npm install --global " + packageJSON.name;
|
||||
return child_process.exec(command, function(error, stdout, stderr) {
|
||||
if (error == null) {
|
||||
return onUpdate(null, stdout, stderr);
|
||||
}
|
||||
if (_.any([error.code === 3, error.code === 'EPERM', error.code === 'ACCES'])) {
|
||||
return president.execute(command, onUpdate);
|
||||
}
|
||||
return done(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
43
build/actions/wizard.js
Normal file
43
build/actions/wizard.js
Normal file
@ -0,0 +1,43 @@
|
||||
(function() {
|
||||
var Promise, capitano, patterns, resin;
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
patterns = require('../utils/patterns');
|
||||
|
||||
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',
|
||||
permission: 'user',
|
||||
primary: true,
|
||||
action: function(params, options, done) {
|
||||
return Promise["try"](function() {
|
||||
if (params.name != null) {
|
||||
return;
|
||||
}
|
||||
return patterns.selectOrCreateApplication().tap(function(applicationName) {
|
||||
return resin.models.application.has(applicationName).then(function(hasApplication) {
|
||||
if (hasApplication) {
|
||||
return applicationName;
|
||||
}
|
||||
return capitano.runAsync("app create " + applicationName);
|
||||
});
|
||||
}).then(function(applicationName) {
|
||||
return params.name = applicationName;
|
||||
});
|
||||
}).then(function() {
|
||||
return capitano.runAsync("device init --application " + params.name);
|
||||
}).tap(patterns.awaitDevice).then(function(uuid) {
|
||||
return capitano.runAsync("device " + uuid);
|
||||
}).then(function() {
|
||||
return console.log('Your device is ready, start pushing some code!');
|
||||
}).nodeify(done);
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
116
build/app.js
116
build/app.js
@ -1,11 +1,11 @@
|
||||
(function() {
|
||||
var _, actions, async, capitano, changeProjectDirectory, errors, plugins, resin, update;
|
||||
var Promise, _, actions, capitano, errors, plugins, resin, update;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
async = require('async');
|
||||
Promise = require('bluebird');
|
||||
|
||||
capitano = require('capitano');
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
@ -13,17 +13,16 @@
|
||||
|
||||
errors = require('./errors');
|
||||
|
||||
plugins = require('./plugins');
|
||||
plugins = require('./utils/plugins');
|
||||
|
||||
update = require('./update');
|
||||
update = require('./utils/update');
|
||||
|
||||
capitano.permission('user', function(done) {
|
||||
return resin.auth.isLoggedIn(function(isLoggedIn) {
|
||||
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
|
||||
if (!isLoggedIn) {
|
||||
return done(new Error('You have to log in'));
|
||||
throw new Error('You have to log in');
|
||||
}
|
||||
return done();
|
||||
});
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
capitano.command({
|
||||
@ -35,37 +34,12 @@
|
||||
}
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'quiet',
|
||||
description: 'quiet (no output)',
|
||||
boolean: true,
|
||||
alias: 'q'
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'project',
|
||||
parameter: 'path',
|
||||
description: 'project path',
|
||||
alias: 'j'
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'version',
|
||||
description: actions.info.version.description,
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
});
|
||||
|
||||
capitano.globalOption({
|
||||
signature: 'no-color',
|
||||
description: 'disable colour highlighting',
|
||||
boolean: true
|
||||
});
|
||||
|
||||
capitano.command(actions.info.version);
|
||||
|
||||
capitano.command(actions.help.help);
|
||||
|
||||
capitano.command(actions.wizard.wizard);
|
||||
|
||||
capitano.command(actions.auth.login);
|
||||
|
||||
capitano.command(actions.auth.logout);
|
||||
@ -78,38 +52,28 @@
|
||||
|
||||
capitano.command(actions.app.list);
|
||||
|
||||
capitano.command(actions.app.info);
|
||||
|
||||
capitano.command(actions.app.remove);
|
||||
|
||||
capitano.command(actions.app.restart);
|
||||
|
||||
capitano.command(actions.app.associate);
|
||||
|
||||
capitano.command(actions.app.init);
|
||||
capitano.command(actions.app.info);
|
||||
|
||||
capitano.command(actions.device.list);
|
||||
|
||||
capitano.command(actions.device.supported);
|
||||
|
||||
capitano.command(actions.device.rename);
|
||||
|
||||
capitano.command(actions.device.init);
|
||||
|
||||
capitano.command(actions.device.info);
|
||||
|
||||
capitano.command(actions.device.remove);
|
||||
|
||||
capitano.command(actions.device.identify);
|
||||
|
||||
capitano.command(actions.device.await);
|
||||
capitano.command(actions.device.register);
|
||||
|
||||
capitano.command(actions.drive.list);
|
||||
capitano.command(actions.device.info);
|
||||
|
||||
capitano.command(actions.notes.set);
|
||||
|
||||
capitano.command(actions.preferences.preferences);
|
||||
|
||||
capitano.command(actions.keys.list);
|
||||
|
||||
capitano.command(actions.keys.add);
|
||||
@ -126,56 +90,20 @@
|
||||
|
||||
capitano.command(actions.env.remove);
|
||||
|
||||
capitano.command(actions.logs.logs);
|
||||
|
||||
capitano.command(actions.os.download);
|
||||
|
||||
capitano.command(actions.os.install);
|
||||
capitano.command(actions.os.configure);
|
||||
|
||||
capitano.command(actions.examples.list);
|
||||
capitano.command(actions.os.initialize);
|
||||
|
||||
capitano.command(actions.examples.clone);
|
||||
capitano.command(actions.logs);
|
||||
|
||||
capitano.command(actions.examples.info);
|
||||
update.notify();
|
||||
|
||||
capitano.command(actions.plugin.list);
|
||||
|
||||
capitano.command(actions.plugin.install);
|
||||
|
||||
capitano.command(actions.plugin.update);
|
||||
|
||||
capitano.command(actions.plugin.remove);
|
||||
|
||||
capitano.command(actions.update.update);
|
||||
|
||||
changeProjectDirectory = function(directory) {
|
||||
try {
|
||||
return process.chdir(directory);
|
||||
} catch (_error) {
|
||||
return errors.handle(new Error("Invalid project: " + directory));
|
||||
}
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
return update.check(callback);
|
||||
}, function(callback) {
|
||||
return plugins.register('resin-plugin-', callback);
|
||||
}, function(callback) {
|
||||
var cli;
|
||||
cli = capitano.parse(process.argv);
|
||||
if (cli.global.quiet) {
|
||||
console.info = _.noop;
|
||||
}
|
||||
if (cli.global.project != null) {
|
||||
changeProjectDirectory(cli.global.project);
|
||||
}
|
||||
if (cli.global.version) {
|
||||
return actions.info.version.action(null, null, callback);
|
||||
} else {
|
||||
return capitano.execute(cli, callback);
|
||||
}
|
||||
}
|
||||
], errors.handle);
|
||||
plugins.register(/^resin-plugin-(.+)$/).then(function() {
|
||||
var cli;
|
||||
cli = capitano.parse(process.argv);
|
||||
return capitano.executeAsync(cli);
|
||||
})["catch"](errors.handle);
|
||||
|
||||
}).call(this);
|
||||
|
@ -1,96 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "basic-resin-node-project",
|
||||
"display_name": "Node.js Starter Project",
|
||||
"repository": "https://github.com/resin-io/basic-resin-node-project",
|
||||
"description": "This is a simple Hello, World project for node.js designed to act as a basis for future work. It demonstrates how to install native Linux packages and configure your application."
|
||||
},
|
||||
{
|
||||
"name": "cimon",
|
||||
"display_name": "Cimon",
|
||||
"repository": "https://bitbucket.org/efwe/cimon",
|
||||
"description": "A simple tool for reading temperatures from a USB-enabled thermometer. This project is used as the backend to efwe's awesome temperature visualisation at 123k.de.",
|
||||
"author": "efwe"
|
||||
},
|
||||
{
|
||||
"name": "firebaseDTL",
|
||||
"display_name": "Digital Temperature Logger",
|
||||
"repository": "https://github.com/shaunmulligan/firebaseDTL",
|
||||
"description": "A Firebase-backed Digital Temperature Logger allowing you to connect devices with multiple temperature sensors to a central cloud-based datastore.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "digitiser",
|
||||
"display_name": "Digitiser",
|
||||
"repository": "https://github.com/shaunmulligan/digitiser",
|
||||
"description": "A tool for displaying integer values from a JSON endpoint on a MAX7219 7-segment display.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "basic-gpio",
|
||||
"display_name": "Example Pi Pins Application",
|
||||
"repository": "https://github.com/shaunmulligan/basic-gpio",
|
||||
"description": "A simple application which demonstrates the use of the Pi Pins library to interface with GPIO.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "coder",
|
||||
"display_name": "Google Coder",
|
||||
"repository": "https://github.com/resin-io/coder",
|
||||
"description": "Resin.io-enabled version of Google's excellent Coder project which makes it easy to develop web projects on your device."
|
||||
},
|
||||
{
|
||||
"name": "hoversnap",
|
||||
"display_name": "Hoversnap",
|
||||
"repository": "https://github.com/resin-io/hoversnap",
|
||||
"description": "A tool for controlling a camera using a foot switch in order to capture shots in which people appear to be flying."
|
||||
},
|
||||
{
|
||||
"name": "resin-piminer",
|
||||
"display_name": "Pi Miner",
|
||||
"repository": "https://github.com/csquared/resin-piminer",
|
||||
"description": "A bitcoin miner for the Raspberry Pi.",
|
||||
"author": "Chris Continanza"
|
||||
},
|
||||
{
|
||||
"name": "resin-cctv",
|
||||
"display_name": "Resin CCTV",
|
||||
"repository": "https://github.com/abresas/resin-cctv",
|
||||
"description": "A project which allows you to use your devices as a CCTV camera system which hooks into Dropbox.",
|
||||
"author": "Aleksis Brezas"
|
||||
},
|
||||
{
|
||||
"name": "resin_player",
|
||||
"display_name": "Resin Player",
|
||||
"repository": "https://bitbucket.org/lifeeth/resin_player/",
|
||||
"description": "A project which allows you to play squeezebox media through your devices.",
|
||||
"author": "Praneeth Bodduluri"
|
||||
},
|
||||
{
|
||||
"name": "salesforceTemp",
|
||||
"display_name": "Salesforce Temperature Probe",
|
||||
"repository": "https://github.com/shaunmulligan/salesforceTemp",
|
||||
"description": "Example application for interfacing with a temperature probe using Salesforce.com.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "sms2speech",
|
||||
"display_name": "SMS to Speech",
|
||||
"repository": "https://github.com/alexandrosm/sms2speech",
|
||||
"description": "A simple tool which uses Twillio to read out incoming SMS messages.",
|
||||
"author": "Alexandros Marinos"
|
||||
},
|
||||
{
|
||||
"name": "resin-kiosk",
|
||||
"display_name": "Simple Digitiser Kiosk",
|
||||
"repository": "https://bitbucket.org/lifeeth/resin-kiosk",
|
||||
"description": "Displays values from a JSON endpoint on your browser in kiosk mode",
|
||||
"author": "Praneeth Bodduluri"
|
||||
},
|
||||
{
|
||||
"name": "text2speech",
|
||||
"display_name": "Text to Speech Converter",
|
||||
"repository": "https://github.com/resin-io/text2speech",
|
||||
"description": "A simple application that makes your device speak out loud."
|
||||
}
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
(function() {
|
||||
var _, isWindows, os, path;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
os = require('os');
|
||||
|
||||
path = require('path');
|
||||
|
||||
isWindows = function() {
|
||||
return os.platform() === 'win32';
|
||||
};
|
||||
|
||||
exports.shouldElevate = function(error) {
|
||||
return _.all([isWindows(), error.code === 'EPERM' || error.code === 'EACCES']);
|
||||
};
|
||||
|
||||
exports.run = function(command) {
|
||||
if (!isWindows()) {
|
||||
return;
|
||||
}
|
||||
return require('windosu').exec(command);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,47 +1,23 @@
|
||||
(function() {
|
||||
var _, os;
|
||||
var chalk, errors, patterns;
|
||||
|
||||
_ = require('lodash');
|
||||
chalk = require('chalk');
|
||||
|
||||
os = require('os');
|
||||
errors = require('resin-cli-errors');
|
||||
|
||||
exports.handle = function(error, exit) {
|
||||
var errorCode, message;
|
||||
if (exit == null) {
|
||||
exit = true;
|
||||
}
|
||||
if ((error == null) || !(error instanceof Error)) {
|
||||
patterns = require('./utils/patterns');
|
||||
|
||||
exports.handle = function(error) {
|
||||
var message;
|
||||
message = errors.interpret(error);
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
if (process.env.DEBUG) {
|
||||
console.error(error.stack);
|
||||
} else {
|
||||
if (error.code === 'EISDIR') {
|
||||
console.error("File is a directory: " + error.path);
|
||||
} else if (error.code === 'ENOENT') {
|
||||
console.error("No such file or directory: " + error.path);
|
||||
} else if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
message = 'You don\'t have enough privileges to run this operation.\n';
|
||||
if (os.platform() === 'win32') {
|
||||
message += 'Run a new Command Prompt as administrator and try running this command again.';
|
||||
} else {
|
||||
message += 'Try running this command again prefixing it with `sudo`.';
|
||||
}
|
||||
console.error(message);
|
||||
} else if (error.code === 'ENOGIT') {
|
||||
console.error('Git is not installed on this system.\nHead over to http://git-scm.com to install it and run this command again.');
|
||||
} else if (error.message != null) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
if (_.isNumber(error.exitCode)) {
|
||||
errorCode = error.exitCode;
|
||||
} else {
|
||||
errorCode = 1;
|
||||
}
|
||||
if (exit) {
|
||||
return process.exit(errorCode);
|
||||
message = error.stack;
|
||||
}
|
||||
patterns.printErrorMessage(message);
|
||||
return process.exit(error.exitCode || 1);
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
|
38
build/npm.js
38
build/npm.js
@ -1,38 +0,0 @@
|
||||
(function() {
|
||||
var _, async, npm;
|
||||
|
||||
npm = require('npm');
|
||||
|
||||
async = require('async');
|
||||
|
||||
_ = require('lodash-contrib');
|
||||
|
||||
exports.getLatestVersion = function(name, callback) {
|
||||
return async.waterfall([
|
||||
function(callback) {
|
||||
var options;
|
||||
options = {
|
||||
loglevel: 'silent',
|
||||
global: true
|
||||
};
|
||||
return npm.load(options, _.unary(callback));
|
||||
}, function(callback) {
|
||||
return npm.commands.view([name], true, function(error, data) {
|
||||
var versions;
|
||||
versions = _.keys(data);
|
||||
return callback(error, _.first(versions));
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
exports.isUpdated = function(name, currentVersion, callback) {
|
||||
return exports.getLatestVersion(name, function(error, latestVersion) {
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
return callback(null, currentVersion === latestVersion);
|
||||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,55 +0,0 @@
|
||||
(function() {
|
||||
var Nplugm, _, capitano, nplugm, registerPlugin;
|
||||
|
||||
Nplugm = require('nplugm');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
nplugm = null;
|
||||
|
||||
registerPlugin = function(plugin) {
|
||||
if (!_.isArray(plugin)) {
|
||||
return capitano.command(plugin);
|
||||
}
|
||||
return _.each(plugin, capitano.command);
|
||||
};
|
||||
|
||||
exports.register = function(prefix, callback) {
|
||||
nplugm = new Nplugm(prefix);
|
||||
return nplugm.list(function(error, plugins) {
|
||||
var i, len, plugin;
|
||||
if (error != null) {
|
||||
return callback(error);
|
||||
}
|
||||
for (i = 0, len = plugins.length; i < len; i++) {
|
||||
plugin = plugins[i];
|
||||
try {
|
||||
registerPlugin(nplugm.require(plugin));
|
||||
} catch (_error) {
|
||||
error = _error;
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
exports.list = function() {
|
||||
return nplugm.list.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
exports.install = function() {
|
||||
return nplugm.install.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
exports.update = function() {
|
||||
return nplugm.update.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
exports.remove = function() {
|
||||
return nplugm.remove.apply(nplugm, arguments);
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -1,37 +0,0 @@
|
||||
(function() {
|
||||
var packageJSON, updateAction, updateNotifier;
|
||||
|
||||
updateNotifier = require('update-notifier');
|
||||
|
||||
packageJSON = require('../package.json');
|
||||
|
||||
updateAction = require('./actions/update');
|
||||
|
||||
exports.perform = function(callback) {
|
||||
return updateAction.update.action(null, null, callback);
|
||||
};
|
||||
|
||||
exports.notify = function(update) {
|
||||
if (!process.stdout.isTTY) {
|
||||
return;
|
||||
}
|
||||
return console.log("> Major update available: " + update.current + " -> " + update.latest + "\n> Run resin update to update.\n> Beware that a major release might introduce breaking changes.\n");
|
||||
};
|
||||
|
||||
exports.check = function(callback) {
|
||||
var notifier;
|
||||
notifier = updateNotifier({
|
||||
pkg: packageJSON
|
||||
});
|
||||
if (notifier.update == null) {
|
||||
return callback();
|
||||
}
|
||||
if (notifier.update.type === 'major') {
|
||||
exports.notify(notifier.update);
|
||||
return callback();
|
||||
}
|
||||
console.log("Performing " + notifier.update.type + " update, hold tight...");
|
||||
return exports.perform(callback);
|
||||
};
|
||||
|
||||
}).call(this);
|
54
build/utils/helpers.js
Normal file
54
build/utils/helpers.js
Normal file
@ -0,0 +1,54 @@
|
||||
(function() {
|
||||
var Promise, _, capitano, chalk, child_process, os, rindle;
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
capitano = Promise.promisifyAll(require('capitano'));
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
_.str = require('underscore.string');
|
||||
|
||||
child_process = require('child_process');
|
||||
|
||||
rindle = require('rindle');
|
||||
|
||||
os = require('os');
|
||||
|
||||
chalk = require('chalk');
|
||||
|
||||
exports.getGroupDefaults = function(group) {
|
||||
return _.chain(group).get('options').map(function(question) {
|
||||
return [question.name, question["default"]];
|
||||
}).object().value();
|
||||
};
|
||||
|
||||
exports.stateToString = function(state) {
|
||||
var percentage, result;
|
||||
percentage = _.str.lpad(state.percentage, 3, '0') + '%';
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
exports.sudo = function(command) {
|
||||
var spawn;
|
||||
if (os.platform() === 'win32') {
|
||||
return capitano.runAsync(command.join(' '));
|
||||
}
|
||||
command = _.union(_.take(process.argv, 2), command);
|
||||
spawn = child_process.spawn('sudo', command, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
return rindle.wait(spawn);
|
||||
};
|
||||
|
||||
}).call(this);
|
113
build/utils/patterns.js
Normal file
113
build/utils/patterns.js
Normal file
@ -0,0 +1,113 @@
|
||||
(function() {
|
||||
var Promise, _, chalk, form, resin, validation, visuals;
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
Promise = require('bluebird');
|
||||
|
||||
form = require('resin-cli-form');
|
||||
|
||||
visuals = require('resin-cli-visuals');
|
||||
|
||||
resin = require('resin-sdk');
|
||||
|
||||
chalk = require('chalk');
|
||||
|
||||
validation = require('./validation');
|
||||
|
||||
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) {
|
||||
throw new Error('You don\'t have any applications');
|
||||
}
|
||||
return resin.models.application.getAll().then(function(applications) {
|
||||
return form.ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: _.pluck(applications, 'app_name')
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.selectOrCreateApplication = 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',
|
||||
validate: validation.validateApplicationName
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.awaitDevice = function(uuid) {
|
||||
return resin.models.device.getName(uuid).then(function(deviceName) {
|
||||
var poll, spinner;
|
||||
spinner = new visuals.Spinner("Waiting for " + deviceName + " to come online");
|
||||
poll = function() {
|
||||
return resin.models.device.isOnline(uuid).then(function(isOnline) {
|
||||
if (isOnline) {
|
||||
spinner.stop();
|
||||
console.info("Device became online: " + deviceName);
|
||||
} else {
|
||||
spinner.start();
|
||||
return Promise.delay(3000).then(poll);
|
||||
}
|
||||
});
|
||||
};
|
||||
console.info("Waiting for " + deviceName + " to connect to resin...");
|
||||
return poll()["return"](uuid);
|
||||
});
|
||||
};
|
||||
|
||||
exports.printErrorMessage = function(message) {
|
||||
return console.error(chalk.red(message));
|
||||
};
|
||||
|
||||
}).call(this);
|
26
build/utils/plugins.js
Normal file
26
build/utils/plugins.js
Normal file
@ -0,0 +1,26 @@
|
||||
(function() {
|
||||
var _, capitano, nplugm, patterns;
|
||||
|
||||
nplugm = require('nplugm');
|
||||
|
||||
_ = require('lodash');
|
||||
|
||||
capitano = require('capitano');
|
||||
|
||||
patterns = require('./patterns');
|
||||
|
||||
exports.register = function(regex) {
|
||||
return nplugm.list(regex).map(function(plugin) {
|
||||
var command;
|
||||
command = require(plugin);
|
||||
command.plugin = true;
|
||||
if (!_.isArray(command)) {
|
||||
return capitano.command(command);
|
||||
}
|
||||
return _.each(command, capitano.command);
|
||||
})["catch"](function(error) {
|
||||
return patterns.printErrorMessage(error.message);
|
||||
});
|
||||
};
|
||||
|
||||
}).call(this);
|
32
build/utils/update.js
Normal file
32
build/utils/update.js
Normal file
@ -0,0 +1,32 @@
|
||||
(function() {
|
||||
var isRoot, notifier, packageJSON, updateNotifier;
|
||||
|
||||
updateNotifier = require('update-notifier');
|
||||
|
||||
isRoot = require('is-root');
|
||||
|
||||
packageJSON = require('../../package.json');
|
||||
|
||||
if (!isRoot()) {
|
||||
notifier = updateNotifier({
|
||||
pkg: packageJSON
|
||||
});
|
||||
}
|
||||
|
||||
exports.hasAvailableUpdate = function() {
|
||||
return notifier != null;
|
||||
};
|
||||
|
||||
exports.notify = function() {
|
||||
if (!exports.hasAvailableUpdate()) {
|
||||
return;
|
||||
}
|
||||
notifier.notify({
|
||||
defer: false
|
||||
});
|
||||
if (notifier.update != null) {
|
||||
return console.log('Notice that you might need administrator privileges depending on your setup\n');
|
||||
}
|
||||
};
|
||||
|
||||
}).call(this);
|
27
build/utils/validation.js
Normal file
27
build/utils/validation.js
Normal file
@ -0,0 +1,27 @@
|
||||
(function() {
|
||||
var validEmail;
|
||||
|
||||
validEmail = require('valid-email');
|
||||
|
||||
exports.validateEmail = function(input) {
|
||||
if (!validEmail(input)) {
|
||||
return 'Email is not valid';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.validatePassword = function(input) {
|
||||
if (input.length < 8) {
|
||||
return 'Password should be 8 characters long';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.validateApplicationName = function(input) {
|
||||
if (input.length < 4) {
|
||||
return 'The application name should be at least 4 characters';
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
}).call(this);
|
@ -20,24 +20,12 @@
|
||||
"lib/actions/device.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Drive",
|
||||
"files": [
|
||||
"lib/actions/drive.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Environment Variables",
|
||||
"files": [
|
||||
"lib/actions/environment-variables.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Examples",
|
||||
"files": [
|
||||
"lib/actions/examples.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Help",
|
||||
"files": [
|
||||
@ -75,21 +63,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin",
|
||||
"title": "Wizard",
|
||||
"files": [
|
||||
"lib/actions/plugin.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Preferences",
|
||||
"files": [
|
||||
"lib/actions/preferences.coffee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Update",
|
||||
"files": [
|
||||
"lib/actions/update.coffee"
|
||||
"lib/actions/wizard.coffee"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -1,49 +0,0 @@
|
||||
_resin() {
|
||||
COMPREPLY=()
|
||||
|
||||
local current="${COMP_WORDS[COMP_CWORD]}"
|
||||
local previous="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
local options="version help login logout signup drive whoami app apps init devices device note preferences keys key envs env logs os examples example"
|
||||
|
||||
case "${previous}" in
|
||||
app)
|
||||
local subcommands="create rm restart"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
drive)
|
||||
local subcommands="list"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
devices)
|
||||
local subcommands="supported"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
device)
|
||||
local subcommands="rename rm identify init"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
key)
|
||||
local subcommands="add rm"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
env)
|
||||
local subcommands="add rename rm"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
os)
|
||||
local subcommands="download install"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
example)
|
||||
local subcommands="clone"
|
||||
COMPREPLY=( $(compgen -W "${subcommands}" -- ${current}) )
|
||||
return 0 ;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
COMPREPLY=( $(compgen -W "${options}" -- ${current}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _resin resin
|
484
doc/cli.markdown
484
doc/cli.markdown
@ -16,91 +16,69 @@ Now you have access to all the commands referenced below.
|
||||
|
||||
- Application
|
||||
|
||||
- [app create <name>](#/pages/using/cli.md#app-create-60-name-62-)
|
||||
- [apps](#/pages/using/cli.md#apps)
|
||||
- [app <name>](#/pages/using/cli.md#app-60-name-62-)
|
||||
- [app restart <name>](#/pages/using/cli.md#app-restart-60-name-62-)
|
||||
- [app rm <name>](#/pages/using/cli.md#app-rm-60-name-62-)
|
||||
- [app associate <name>](#/pages/using/cli.md#app-associate-60-name-62-)
|
||||
- [init](#/pages/using/cli.md#init)
|
||||
- [app create <name>](#app-create-60-name-62-)
|
||||
- [apps](#apps)
|
||||
- [app <name>](#app-60-name-62-)
|
||||
- [app restart <name>](#app-restart-60-name-62-)
|
||||
- [app rm <name>](#app-rm-60-name-62-)
|
||||
- [app associate <name>](#app-associate-60-name-62-)
|
||||
|
||||
- Authentication
|
||||
|
||||
- [login [token]](#/pages/using/cli.md#login-token-)
|
||||
- [logout](#/pages/using/cli.md#logout)
|
||||
- [signup](#/pages/using/cli.md#signup)
|
||||
- [whoami](#/pages/using/cli.md#whoami)
|
||||
- [login [token]](#login-token-)
|
||||
- [logout](#logout)
|
||||
- [signup](#signup)
|
||||
- [whoami](#whoami)
|
||||
|
||||
- Device
|
||||
|
||||
- [devices](#/pages/using/cli.md#devices)
|
||||
- [device <name>](#/pages/using/cli.md#device-60-name-62-)
|
||||
- [device rm <name>](#/pages/using/cli.md#device-rm-60-name-62-)
|
||||
- [device identify <uuid>](#/pages/using/cli.md#device-identify-60-uuid-62-)
|
||||
- [device rename <name> [newName]](#/pages/using/cli.md#device-rename-60-name-62-newname-)
|
||||
- [devices supported](#/pages/using/cli.md#devices-supported)
|
||||
- [device await <name>](#/pages/using/cli.md#device-await-60-name-62-)
|
||||
- [device init [device]](#/pages/using/cli.md#device-init-device-)
|
||||
|
||||
- Drive
|
||||
|
||||
- [drives](#/pages/using/cli.md#drives)
|
||||
- [devices](#devices)
|
||||
- [device <uuid>](#device-60-uuid-62-)
|
||||
- [device register <application>](#device-register-60-application-62-)
|
||||
- [device rm <uuid>](#device-rm-60-uuid-62-)
|
||||
- [device identify <uuid>](#device-identify-60-uuid-62-)
|
||||
- [device rename <uuid> [newName]](#device-rename-60-uuid-62-newname-)
|
||||
- [device init](#device-init)
|
||||
|
||||
- Environment Variables
|
||||
|
||||
- [envs](#/pages/using/cli.md#envs)
|
||||
- [env rm <id>](#/pages/using/cli.md#env-rm-60-id-62-)
|
||||
- [env add <key> [value]](#/pages/using/cli.md#env-add-60-key-62-value-)
|
||||
- [env rename <id> <value>](#/pages/using/cli.md#env-rename-60-id-62-60-value-62-)
|
||||
|
||||
- Examples
|
||||
|
||||
- [examples](#/pages/using/cli.md#examples)
|
||||
- [example <id>](#/pages/using/cli.md#example-60-id-62-)
|
||||
- [example clone <id>](#/pages/using/cli.md#example-clone-60-id-62-)
|
||||
- [envs](#envs)
|
||||
- [env rm <id>](#env-rm-60-id-62-)
|
||||
- [env add <key> [value]](#env-add-60-key-62-value-)
|
||||
- [env rename <id> <value>](#env-rename-60-id-62-60-value-62-)
|
||||
|
||||
- Help
|
||||
|
||||
- [help [command...]](#/pages/using/cli.md#help-command-)
|
||||
- [help [command...]](#help-command-)
|
||||
|
||||
- Information
|
||||
|
||||
- [version](#/pages/using/cli.md#version)
|
||||
- [version](#version)
|
||||
|
||||
- Keys
|
||||
|
||||
- [keys](#/pages/using/cli.md#keys)
|
||||
- [key <id>](#/pages/using/cli.md#key-60-id-62-)
|
||||
- [key rm <id>](#/pages/using/cli.md#key-rm-60-id-62-)
|
||||
- [key add <name> [path]](#/pages/using/cli.md#key-add-60-name-62-path-)
|
||||
- [keys](#keys)
|
||||
- [key <id>](#key-60-id-62-)
|
||||
- [key rm <id>](#key-rm-60-id-62-)
|
||||
- [key add <name> [path]](#key-add-60-name-62-path-)
|
||||
|
||||
- Logs
|
||||
|
||||
- [logs <uuid>](#/pages/using/cli.md#logs-60-uuid-62-)
|
||||
- [logs <uuid>](#logs-60-uuid-62-)
|
||||
|
||||
- Notes
|
||||
|
||||
- [note <|note>](#/pages/using/cli.md#note-60-note-62-)
|
||||
- [note <|note>](#note-60-note-62-)
|
||||
|
||||
- OS
|
||||
|
||||
- [os download <name>](#/pages/using/cli.md#os-download-60-name-62-)
|
||||
- [os install <image> [device]](#/pages/using/cli.md#os-install-60-image-62-device-)
|
||||
- [os download <type>](#os-download-60-type-62-)
|
||||
- [os configure <image> <uuid>](#os-configure-60-image-62-60-uuid-62-)
|
||||
- [os initialize <image>](#os-initialize-60-image-62-)
|
||||
|
||||
- Plugin
|
||||
- Wizard
|
||||
|
||||
- [plugins](#/pages/using/cli.md#plugins)
|
||||
- [plugin install <name>](#/pages/using/cli.md#plugin-install-60-name-62-)
|
||||
- [plugin update <name>](#/pages/using/cli.md#plugin-update-60-name-62-)
|
||||
- [plugin rm <name>](#/pages/using/cli.md#plugin-rm-60-name-62-)
|
||||
|
||||
- Preferences
|
||||
|
||||
- [preferences](#/pages/using/cli.md#preferences)
|
||||
|
||||
- Update
|
||||
|
||||
- [update](#/pages/using/cli.md#update)
|
||||
- [quickstart [name]](#quickstart-name-)
|
||||
|
||||
# Application
|
||||
|
||||
@ -177,24 +155,18 @@ Use this command to associate a project directory with a resin application.
|
||||
|
||||
This command adds a 'resin' git remote to the directory and runs git init if necessary.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin app associate MyApp
|
||||
$ resin app associate MyApp --project my/app/directory
|
||||
|
||||
## init
|
||||
### Options
|
||||
|
||||
Use this command to initialise a directory as a resin application.
|
||||
#### --yes, -y
|
||||
|
||||
This command performs the following steps:
|
||||
- Create a resin.io application.
|
||||
- Initialize the current directory as a git repository.
|
||||
- Add the corresponding git remote to the application.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin init
|
||||
$ resin init --project my/app/directory
|
||||
confirm non interactively
|
||||
|
||||
# Authentication
|
||||
|
||||
@ -202,9 +174,7 @@ Examples:
|
||||
|
||||
Use this command to login to your resin.io account.
|
||||
|
||||
To login, you need your token, which is accesible from the preferences page:
|
||||
|
||||
https://dashboard.resin.io/preferences
|
||||
To login, you need your token, which is accesible from the preferences page.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -232,28 +202,12 @@ Examples:
|
||||
Username: johndoe
|
||||
Password: ***********
|
||||
|
||||
$ resin signup --email me@mycompany.com --username johndoe --password ***********
|
||||
|
||||
$ resin whoami
|
||||
johndoe
|
||||
|
||||
### Options
|
||||
|
||||
#### --email, -e <email>
|
||||
|
||||
user email
|
||||
|
||||
#### --username, -u <username>
|
||||
|
||||
user name
|
||||
|
||||
#### --password, -p <user password>
|
||||
|
||||
user password
|
||||
|
||||
## whoami
|
||||
|
||||
Use this command to find out the current logged in username.
|
||||
Use this command to find out the current logged in username and email address.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -280,15 +234,29 @@ Examples:
|
||||
|
||||
application name
|
||||
|
||||
## device <name>
|
||||
## device <uuid>
|
||||
|
||||
Use this command to show information about a single device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device MyDevice
|
||||
$ resin device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
|
||||
## device rm <name>
|
||||
## device register <application>
|
||||
|
||||
Use this command to register a device to an application.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device register MyApp
|
||||
|
||||
### Options
|
||||
|
||||
#### --uuid, -u <uuid>
|
||||
|
||||
custom uuid
|
||||
|
||||
## device rm <uuid>
|
||||
|
||||
Use this command to remove a device from resin.io.
|
||||
|
||||
@ -297,8 +265,8 @@ You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rm MyDevice
|
||||
$ resin device rm MyDevice --yes
|
||||
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --yes
|
||||
|
||||
### Options
|
||||
|
||||
@ -316,7 +284,7 @@ Examples:
|
||||
|
||||
$ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
|
||||
|
||||
## device rename <name> [newName]
|
||||
## device rename <uuid> [newName]
|
||||
|
||||
Use this command to rename a device.
|
||||
|
||||
@ -324,70 +292,20 @@ If you omit the name, you'll get asked for it interactively.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rename MyDevice MyPi
|
||||
$ resin device rename MyDevice
|
||||
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 MyPi
|
||||
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
|
||||
## devices supported
|
||||
|
||||
Use this command to get the list of all supported devices
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin devices supported
|
||||
|
||||
## device await <name>
|
||||
|
||||
Use this command to await for a device to become online.
|
||||
|
||||
The process will exit when the device becomes online.
|
||||
|
||||
Notice that there is no time limit for this command, so it might run forever.
|
||||
|
||||
You can configure the poll interval with the --interval option (defaults to 3000ms).
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device await MyDevice
|
||||
$ resin device await MyDevice --interval 1000
|
||||
|
||||
### Options
|
||||
|
||||
#### --interval, -i <interval>
|
||||
|
||||
poll interval
|
||||
|
||||
## device init [device]
|
||||
## device init
|
||||
|
||||
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.
|
||||
|
||||
If `device` is omitted, you will be prompted to select a device interactively.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
Notice this command may ask for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
You can quiet the progress bar by passing the `--quiet` boolean option.
|
||||
|
||||
You may have to unmount the device before attempting this operation.
|
||||
|
||||
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:
|
||||
|
||||
$ resin device init
|
||||
$ 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
|
||||
|
||||
@ -395,43 +313,30 @@ Examples:
|
||||
|
||||
application name
|
||||
|
||||
#### --network, -n <network>
|
||||
#### --yes, -y
|
||||
|
||||
network type
|
||||
confirm non interactively
|
||||
|
||||
#### --ssid, -s <ssid>
|
||||
#### --advanced, -v
|
||||
|
||||
wifi ssid, if network is wifi
|
||||
|
||||
#### --key, -k <key>
|
||||
|
||||
wifi key, if network is wifi
|
||||
|
||||
# Drive
|
||||
|
||||
## drives
|
||||
|
||||
Use this command to list all drives that are connected to your machine.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin drives
|
||||
enable advanced configuration
|
||||
|
||||
# Environment Variables
|
||||
|
||||
## envs
|
||||
|
||||
Use this command to list all environment variables for a particular application.
|
||||
Notice we will support per-device environment variables soon.
|
||||
Use this command to list all environment variables for
|
||||
a particular application or device.
|
||||
|
||||
This command lists all custom environment variables set on the devices running
|
||||
the application. If you want to see all environment variables, including private
|
||||
This command lists all custom environment variables.
|
||||
If you want to see all environment variables, including private
|
||||
ones used by resin, use the verbose option.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin envs --application 91
|
||||
$ resin envs --application 91 --verbose
|
||||
$ resin envs --application MyApp
|
||||
$ resin envs --application MyApp --verbose
|
||||
$ resin envs --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
|
||||
### Options
|
||||
|
||||
@ -439,6 +344,10 @@ Example:
|
||||
|
||||
application name
|
||||
|
||||
#### --device, -d <device>
|
||||
|
||||
device name
|
||||
|
||||
#### --verbose, -v
|
||||
|
||||
show private environment variables
|
||||
@ -452,10 +361,13 @@ Don't remove resin specific variables, as things might not work as expected.
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
If you want to eliminate a device environment variable, pass the `--device` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env rm 215
|
||||
$ resin env rm 215 --yes
|
||||
$ resin env rm 215 --device
|
||||
|
||||
### Options
|
||||
|
||||
@ -463,22 +375,28 @@ Examples:
|
||||
|
||||
confirm non interactively
|
||||
|
||||
#### --device, -d
|
||||
|
||||
device name
|
||||
|
||||
## env add <key> [value]
|
||||
|
||||
Use this command to add an enviroment variable to an application.
|
||||
|
||||
You need to pass the `--application` option.
|
||||
|
||||
If value is omitted, the tool will attempt to use the variable's value
|
||||
as defined in your host machine.
|
||||
|
||||
Use the `--device` option if you want to assign the environment variable
|
||||
to a specific device.
|
||||
|
||||
If the value is grabbed from the environment, a warning message will be printed.
|
||||
Use `--quiet` to remove it.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env add EDITOR vim -a 91
|
||||
$ resin env add TERM -a 91
|
||||
$ resin env add EDITOR vim --application MyApp
|
||||
$ resin env add TERM --application MyApp
|
||||
$ resin env add EDITOR vim --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
|
||||
### Options
|
||||
|
||||
@ -486,42 +404,26 @@ Examples:
|
||||
|
||||
application name
|
||||
|
||||
#### --device, -d <device>
|
||||
|
||||
device name
|
||||
|
||||
## env rename <id> <value>
|
||||
|
||||
Use this command to rename an enviroment variable from an application.
|
||||
|
||||
Pass the `--device` boolean option if you want to rename a device environment variable.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env rename 376 emacs
|
||||
$ resin env rename 376 emacs --device
|
||||
|
||||
# Examples
|
||||
### Options
|
||||
|
||||
## examples
|
||||
#### --device, -d
|
||||
|
||||
Use this command to list available example applications from resin.io
|
||||
|
||||
Example:
|
||||
|
||||
$ resin examples
|
||||
|
||||
## example <id>
|
||||
|
||||
Use this command to show information of a single example application
|
||||
|
||||
Example:
|
||||
|
||||
$ resin example 3
|
||||
|
||||
## example clone <id>
|
||||
|
||||
Use this command to clone an example application to the current directory
|
||||
|
||||
This command outputs information about the cloning process.
|
||||
Use `--quiet` to remove that output.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin example clone 3
|
||||
device name
|
||||
|
||||
# Help
|
||||
|
||||
@ -534,6 +436,12 @@ Examples:
|
||||
$ resin help apps
|
||||
$ resin help os download
|
||||
|
||||
### Options
|
||||
|
||||
#### --verbose, -v
|
||||
|
||||
show additional commands
|
||||
|
||||
# Information
|
||||
|
||||
## version
|
||||
@ -596,9 +504,6 @@ Use this command to show logs for a specific device.
|
||||
|
||||
By default, the command prints all log messages and exit.
|
||||
|
||||
To limit the output to the n last lines, use the `--num` option along with a number.
|
||||
This is similar to doing `resin logs <uuid> | tail -n X`.
|
||||
|
||||
To continuously stream output, and see new logs in real time, use the `--tail` option.
|
||||
|
||||
Note that for now you need to provide the whole UUID for this command to work correctly.
|
||||
@ -608,15 +513,10 @@ This is due to some technical limitations that we plan to address soon.
|
||||
Examples:
|
||||
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail
|
||||
|
||||
### Options
|
||||
|
||||
#### --num, -n <num>
|
||||
|
||||
number of lines to display
|
||||
|
||||
#### --tail, -t
|
||||
|
||||
continuously stream output
|
||||
@ -629,77 +529,56 @@ Use this command to set or update a device note.
|
||||
|
||||
If note command isn't passed, the tool attempts to read from `stdin`.
|
||||
|
||||
To view the notes, use $ resin device <name>.
|
||||
To view the notes, use $ resin device <uuid>.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin note "My useful note" --device MyDevice
|
||||
$ cat note.txt | resin note --device MyDevice
|
||||
$ resin note "My useful note" --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
$ cat note.txt | resin note --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
|
||||
### Options
|
||||
|
||||
#### --device, --d,dev, --d,dev <device>
|
||||
|
||||
device name
|
||||
device uuid
|
||||
|
||||
# OS
|
||||
|
||||
## os download <name>
|
||||
## os download <type>
|
||||
|
||||
Use this command to download the device OS configured to a specific network.
|
||||
|
||||
Ethernet:
|
||||
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
|
||||
|
||||
Wifi:
|
||||
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
|
||||
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
|
||||
|
||||
Alternatively, you can omit all kind of network configuration options to configure interactively.
|
||||
|
||||
You have to specify an output location with the `--output` option.
|
||||
Use this command to download an unconfigured os image for a certain device type.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os download MyApp --output ~/MyResinOS.zip
|
||||
$ resin os download MyApp --network ethernet --output ~/MyResinOS.zip
|
||||
$ resin os download MyApp --network wifi --ssid MyNetwork --key secreykey123 --output ~/MyResinOS.zip
|
||||
$ resin os download MyApp --network ethernet --output ~/MyResinOS.zip
|
||||
$ resin os download parallella -o ../foo/bar/parallella.img
|
||||
|
||||
### Options
|
||||
|
||||
#### --network, -n <network>
|
||||
|
||||
network type
|
||||
|
||||
#### --ssid, -s <ssid>
|
||||
|
||||
wifi ssid, if network is wifi
|
||||
|
||||
#### --key, -k <key>
|
||||
|
||||
wifi key, if network is wifi
|
||||
|
||||
#### --output, -o <output>
|
||||
|
||||
output file
|
||||
output path
|
||||
|
||||
## os install <image> [device]
|
||||
## os configure <image> <uuid>
|
||||
|
||||
Use this command to write an operating system image to a device.
|
||||
|
||||
Note that this command requires admin privileges.
|
||||
|
||||
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 quiet the progress bar by passing the `--quiet` boolean option.
|
||||
Use this command to configure a previously download operating system image with a device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os install rpi.iso /dev/disk2
|
||||
$ resin os configure ../path/rpi.img 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
|
||||
### Options
|
||||
|
||||
#### --advanced, -v
|
||||
|
||||
show advanced commands
|
||||
|
||||
## os initialize <image>
|
||||
|
||||
Use this command to initialize a previously configured operating system image.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
|
||||
|
||||
### Options
|
||||
|
||||
@ -707,82 +586,29 @@ Examples:
|
||||
|
||||
confirm non interactively
|
||||
|
||||
# Plugin
|
||||
#### --type, -t <type>
|
||||
|
||||
## plugins
|
||||
device type
|
||||
|
||||
Use this command to list all the installed resin plugins.
|
||||
#### --drive, -d <drive>
|
||||
|
||||
drive
|
||||
|
||||
# Wizard
|
||||
|
||||
## quickstart [name]
|
||||
|
||||
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:
|
||||
|
||||
$ resin plugins
|
||||
|
||||
## plugin install <name>
|
||||
|
||||
Use this command to install a resin plugin
|
||||
|
||||
Use `--quiet` to prevent information logging.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin install hello
|
||||
|
||||
## plugin update <name>
|
||||
|
||||
Use this command to update a resin plugin
|
||||
|
||||
Use `--quiet` to prevent information logging.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin update hello
|
||||
|
||||
## plugin rm <name>
|
||||
|
||||
Use this command to remove a resin.io plugin.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin rm hello
|
||||
$ resin plugin rm hello --yes
|
||||
|
||||
### Options
|
||||
|
||||
#### --yes, -y
|
||||
|
||||
confirm non interactively
|
||||
|
||||
# Preferences
|
||||
|
||||
## preferences
|
||||
|
||||
Use this command to open the preferences form.
|
||||
|
||||
In the future, we will allow changing all preferences directly from the terminal.
|
||||
For now, we open your default web browser and point it to the web based preferences form.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin preferences
|
||||
|
||||
# Update
|
||||
|
||||
## update
|
||||
|
||||
Use this command to update the Resin CLI
|
||||
|
||||
This command outputs information about the update process.
|
||||
Use `--quiet` to remove that output.
|
||||
|
||||
The Resin CLI checks for updates once per day.
|
||||
|
||||
Major updates require a manual update with this update command,
|
||||
while minor updates are applied automatically.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin update
|
||||
$ sudo resin quickstart
|
||||
$ sudo resin quickstart MyApp
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
_ = require('lodash')
|
||||
path = require('path')
|
||||
capitanodoc = require('../../capitanodoc.json')
|
||||
markdown = require('./markdown')
|
||||
|
||||
@ -13,10 +14,13 @@ for commandCategory in capitanodoc.categories
|
||||
category.commands = []
|
||||
|
||||
for file in commandCategory.files
|
||||
actions = require(file)
|
||||
actions = require(path.join(process.cwd(), file))
|
||||
|
||||
for actionName, actionCommand of actions
|
||||
category.commands.push(_.omit(actionCommand, 'action'))
|
||||
if actions.signature?
|
||||
category.commands.push(_.omit(actions, 'action'))
|
||||
else
|
||||
for actionName, actionCommand of actions
|
||||
category.commands.push(_.omit(actionCommand, 'action'))
|
||||
|
||||
result.categories.push(category)
|
||||
|
||||
@ -25,10 +29,7 @@ result.toc = _.map result.toc, (category) ->
|
||||
category.commands = _.map category.commands, (command) ->
|
||||
return {
|
||||
signature: command.signature
|
||||
|
||||
# TODO: Make anchor prefix a configurable setting
|
||||
# in capitanodoc.json
|
||||
anchor: '#/pages/using/cli.md#' + command.signature
|
||||
anchor: '#' + command.signature
|
||||
.replace(/\s/g,'-')
|
||||
.replace(/</g, '60-')
|
||||
.replace(/>/g, '-62-')
|
||||
|
@ -1,12 +1,9 @@
|
||||
mkdirp = require('mkdirp')
|
||||
path = require('path')
|
||||
gulp = require('gulp')
|
||||
mocha = require('gulp-mocha')
|
||||
coffee = require('gulp-coffee')
|
||||
markedMan = require('gulp-marked-man')
|
||||
coffeelint = require('gulp-coffeelint')
|
||||
shell = require('gulp-shell')
|
||||
mochaNotifierReporter = require('mocha-notifier-reporter')
|
||||
packageJSON = require('./package.json')
|
||||
|
||||
OPTIONS =
|
||||
@ -16,32 +13,20 @@ OPTIONS =
|
||||
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
|
||||
app: [ 'lib/**/*.coffee', '!lib/**/*.spec.coffee' ]
|
||||
tests: 'tests/**/*.spec.coffee'
|
||||
json: [ 'lib/**/*.json' ]
|
||||
man: 'man/**/*.md'
|
||||
directories:
|
||||
man: 'man/'
|
||||
build: 'build/'
|
||||
|
||||
gulp.task 'man', ->
|
||||
gulp.src(OPTIONS.files.man)
|
||||
.pipe(markedMan())
|
||||
.pipe(gulp.dest(OPTIONS.directories.man))
|
||||
|
||||
gulp.task 'test', ->
|
||||
gulp.src(OPTIONS.files.tests, read: false)
|
||||
.pipe(mocha({
|
||||
reporter: mochaNotifierReporter.decorate('landing')
|
||||
reporter: 'min'
|
||||
}))
|
||||
|
||||
gulp.task 'coffee', [ 'test', 'lint', 'json' ], ->
|
||||
gulp.task 'coffee', [ 'test', 'lint' ], ->
|
||||
gulp.src(OPTIONS.files.app)
|
||||
.pipe(coffee())
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'json', ->
|
||||
gulp.src(OPTIONS.files.json)
|
||||
.pipe(gulp.dest(OPTIONS.directories.build))
|
||||
|
||||
gulp.task 'lint', ->
|
||||
gulp.src(OPTIONS.files.coffee)
|
||||
.pipe(coffeelint({
|
||||
@ -51,9 +36,7 @@ gulp.task 'lint', ->
|
||||
|
||||
gulp.task 'build', [
|
||||
'coffee'
|
||||
'man'
|
||||
]
|
||||
|
||||
gulp.task 'watch', [ 'test', 'lint', 'coffee' ], ->
|
||||
gulp.watch([ OPTIONS.files.coffee, OPTIONS.files.json ], [ 'coffee' ])
|
||||
gulp.watch([ OPTIONS.files.man ], [ 'man' ])
|
||||
gulp.watch([ OPTIONS.files.coffee ], [ 'coffee' ])
|
||||
|
@ -1,10 +1,8 @@
|
||||
path = require('path')
|
||||
_ = require('lodash-contrib')
|
||||
async = require('async')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
commandOptions = require('./command-options')
|
||||
vcs = require('resin-vcs')
|
||||
events = require('resin-cli-events')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
exports.create =
|
||||
signature: 'app create <name>'
|
||||
@ -33,26 +31,24 @@ exports.create =
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
resin.models.application.has(params.name, callback)
|
||||
# Validate the the application name is available
|
||||
# before asking the device type.
|
||||
# https://github.com/resin-io/resin-cli/issues/30
|
||||
resin.models.application.has(params.name).then (hasApplication) ->
|
||||
if hasApplication
|
||||
throw new Error('You already have an application with that name!')
|
||||
|
||||
(hasApplication, callback) ->
|
||||
if hasApplication
|
||||
return callback(new Error('You already have an application with that name!'))
|
||||
|
||||
return callback(null, options.type) if options.type?
|
||||
|
||||
resin.models.device.getSupportedDeviceTypes (error, deviceTypes) ->
|
||||
return callback(error) if error?
|
||||
visuals.widgets.select('Select a type', deviceTypes, callback)
|
||||
|
||||
(type, callback) ->
|
||||
resin.models.application.create(params.name, type, callback)
|
||||
|
||||
], done)
|
||||
.then ->
|
||||
return options.type or patterns.selectDeviceType()
|
||||
.then (deviceType) ->
|
||||
return resin.models.application.create(params.name, deviceType)
|
||||
.then (application) ->
|
||||
console.info("Application created: #{application.app_name} (#{application.device_type}, id #{application.id})")
|
||||
events.send('application.create', application: application.id)
|
||||
.nodeify(done)
|
||||
|
||||
exports.list =
|
||||
signature: 'apps'
|
||||
@ -68,17 +64,17 @@ exports.list =
|
||||
$ resin apps
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.getAll (error, applications) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.horizontal applications, [
|
||||
resin.models.application.getAll().then (applications) ->
|
||||
console.log visuals.table.horizontal applications, [
|
||||
'id'
|
||||
'app_name'
|
||||
'device_type'
|
||||
'online_devices'
|
||||
'devices_length'
|
||||
]
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.info =
|
||||
signature: 'app <name>'
|
||||
@ -91,17 +87,18 @@ exports.info =
|
||||
$ resin app MyApp
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.get params.name, (error, application) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.vertical application, [
|
||||
resin.models.application.get(params.name).then (application) ->
|
||||
console.log visuals.table.vertical application, [
|
||||
"$#{application.app_name}$"
|
||||
'id'
|
||||
'app_name'
|
||||
'device_type'
|
||||
'git_repository'
|
||||
'commit'
|
||||
]
|
||||
return done()
|
||||
events.send('application.open', application: application.id)
|
||||
.nodeify(done)
|
||||
|
||||
exports.restart =
|
||||
signature: 'app restart <name>'
|
||||
@ -115,7 +112,7 @@ exports.restart =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.restart(params.name, done)
|
||||
resin.models.application.restart(params.name).nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'app rm <name>'
|
||||
@ -134,79 +131,9 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'application', options.yes, (callback) ->
|
||||
resin.models.application.remove(params.name, callback)
|
||||
, done
|
||||
|
||||
exports.associate =
|
||||
signature: 'app associate <name>'
|
||||
description: 'associate a resin project'
|
||||
help: '''
|
||||
Use this command to associate a project directory with a resin application.
|
||||
|
||||
This command adds a 'resin' git remote to the directory and runs git init if necessary.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin app associate MyApp
|
||||
$ resin app associate MyApp --project my/app/directory
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
currentDirectory = process.cwd()
|
||||
|
||||
async.waterfall [
|
||||
|
||||
(callback) ->
|
||||
vcs.initialize(currentDirectory, callback)
|
||||
|
||||
(callback) ->
|
||||
resin.models.application.get(params.name, callback)
|
||||
|
||||
(application, callback) ->
|
||||
vcs.addRemote(currentDirectory, application.git_repository, callback)
|
||||
|
||||
], (error, remoteUrl) ->
|
||||
return done(error) if error?
|
||||
console.info("git repository added: #{remoteUrl}")
|
||||
return done(null, remoteUrl)
|
||||
|
||||
exports.init =
|
||||
signature: 'init'
|
||||
description: 'init an application'
|
||||
help: '''
|
||||
Use this command to initialise a directory as a resin application.
|
||||
|
||||
This command performs the following steps:
|
||||
- Create a resin.io application.
|
||||
- Initialize the current directory as a git repository.
|
||||
- Add the corresponding git remote to the application.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin init
|
||||
$ resin init --project my/app/directory
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
|
||||
currentDirectory = process.cwd()
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
currentDirectoryBasename = path.basename(currentDirectory)
|
||||
visuals.widgets.ask('What is the name of your application?', currentDirectoryBasename, callback)
|
||||
|
||||
(applicationName, callback) ->
|
||||
|
||||
# TODO: Make resin.models.application.create return
|
||||
# the whole application instead of just the id
|
||||
exports.create.action name: applicationName, options, (error) ->
|
||||
return callback(error) if error?
|
||||
return callback(null, applicationName)
|
||||
|
||||
(applicationName, callback) ->
|
||||
exports.associate.action(name: applicationName, options, callback)
|
||||
|
||||
], done)
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the application?').then ->
|
||||
resin.models.application.remove(params.name)
|
||||
.tap ->
|
||||
resin.models.application.get(params.name).then (application) ->
|
||||
events.send('application.delete', application: application.id)
|
||||
.nodeify(done)
|
||||
|
@ -1,56 +1,65 @@
|
||||
open = require('open')
|
||||
_ = require('lodash-contrib')
|
||||
url = require('url')
|
||||
async = require('async')
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
settings = require('resin-settings-client')
|
||||
form = require('resin-cli-form')
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
TOKEN_URL = url.resolve(settings.get('remoteUrl'), '/preferences')
|
||||
events = require('resin-cli-events')
|
||||
validation = require('../utils/validation')
|
||||
|
||||
exports.login =
|
||||
signature: 'login [token]'
|
||||
signature: 'login'
|
||||
description: 'login to resin.io'
|
||||
help: """
|
||||
help: '''
|
||||
Use this command to login to your resin.io account.
|
||||
|
||||
To login, you need your token, which is accesible from the preferences page:
|
||||
|
||||
#{TOKEN_URL}
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin login
|
||||
$ resin login "eyJ0eXAiOiJKV1Qi..."
|
||||
"""
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'email'
|
||||
parameter: 'email'
|
||||
description: 'email'
|
||||
alias: [ 'e', 'u' ]
|
||||
}
|
||||
{
|
||||
signature: 'password'
|
||||
parameter: 'password'
|
||||
description: 'password'
|
||||
alias: 'p'
|
||||
}
|
||||
]
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
|
||||
console.info """
|
||||
To login to the Resin CLI, you need your unique token, which is accesible from
|
||||
the preferences page at #{TOKEN_URL}
|
||||
|
||||
Attempting to open a browser at that location...
|
||||
"""
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
open TOKEN_URL, (error) ->
|
||||
if error?
|
||||
console.error """
|
||||
Unable to open a web browser in the current environment.
|
||||
Please visit #{TOKEN_URL} manually.
|
||||
"""
|
||||
return callback()
|
||||
|
||||
(callback) ->
|
||||
return callback(null, params.token) if params.token?
|
||||
visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback)
|
||||
|
||||
(token, callback) ->
|
||||
resin.auth.loginWithToken(token, done)
|
||||
|
||||
], done)
|
||||
form.run [
|
||||
message: 'Email:'
|
||||
name: 'email'
|
||||
type: 'input'
|
||||
validate: validation.validateEmail
|
||||
,
|
||||
message: 'Password:'
|
||||
name: 'password'
|
||||
type: 'password'
|
||||
],
|
||||
override: options
|
||||
.then(resin.auth.login)
|
||||
.then(resin.auth.twoFactor.isPassed)
|
||||
.then (isTwoFactorAuthPassed) ->
|
||||
return if isTwoFactorAuthPassed
|
||||
return form.ask
|
||||
message: 'Two factor auth challenge:'
|
||||
name: 'code'
|
||||
type: 'input'
|
||||
.then(resin.auth.twoFactor.challenge)
|
||||
.catch ->
|
||||
resin.auth.logout().then ->
|
||||
throw new Error('Invalid two factor authentication code')
|
||||
.then(resin.auth.whoami)
|
||||
.tap (username) ->
|
||||
console.info("Successfully logged in as: #{username}")
|
||||
events.send('user.login')
|
||||
.nodeify(done)
|
||||
|
||||
exports.logout =
|
||||
signature: 'logout'
|
||||
@ -64,7 +73,9 @@ exports.logout =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.auth.logout(done)
|
||||
resin.auth.logout().then ->
|
||||
events.send('user.logout')
|
||||
.nodeify(done)
|
||||
|
||||
exports.signup =
|
||||
signature: 'signup'
|
||||
@ -81,66 +92,37 @@ exports.signup =
|
||||
Username: johndoe
|
||||
Password: ***********
|
||||
|
||||
$ resin signup --email me@mycompany.com --username johndoe --password ***********
|
||||
|
||||
$ resin whoami
|
||||
johndoe
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'email'
|
||||
parameter: 'email'
|
||||
description: 'user email'
|
||||
alias: 'e'
|
||||
}
|
||||
{
|
||||
signature: 'username'
|
||||
parameter: 'username'
|
||||
description: 'user name'
|
||||
alias: 'u'
|
||||
}
|
||||
{
|
||||
signature: 'password'
|
||||
parameter: 'user password'
|
||||
description: 'user password'
|
||||
alias: 'p'
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
form.run [
|
||||
message: 'Email:'
|
||||
name: 'email'
|
||||
type: 'input'
|
||||
validate: validation.validateEmail
|
||||
,
|
||||
message: 'Username:'
|
||||
name: 'username'
|
||||
type: 'input'
|
||||
,
|
||||
message: 'Password:'
|
||||
name: 'password'
|
||||
type: 'password',
|
||||
validate: validation.validatePassword
|
||||
]
|
||||
|
||||
hasOptionCredentials = not _.isEmpty(options)
|
||||
|
||||
if hasOptionCredentials
|
||||
|
||||
if not options.email?
|
||||
return done(new Error('Missing email'))
|
||||
|
||||
if not options.username?
|
||||
return done(new Error('Missing username'))
|
||||
|
||||
if not options.password?
|
||||
return done(new Error('Missing password'))
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
return callback(null, options) if hasOptionCredentials
|
||||
visuals.widgets.register(callback)
|
||||
|
||||
(credentials, callback) ->
|
||||
resin.auth.register credentials, (error, token) ->
|
||||
return callback(error, credentials)
|
||||
|
||||
(credentials, callback) ->
|
||||
resin.auth.login(credentials, callback)
|
||||
|
||||
], done)
|
||||
.then(resin.auth.register)
|
||||
.then(resin.auth.loginWithToken)
|
||||
.tap ->
|
||||
events.send('user.signup')
|
||||
.nodeify(done)
|
||||
|
||||
exports.whoami =
|
||||
signature: 'whoami'
|
||||
description: 'get current username'
|
||||
description: 'get current username and email address'
|
||||
help: '''
|
||||
Use this command to find out the current logged in username.
|
||||
Use this command to find out the current logged in username and email address.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -148,11 +130,13 @@ exports.whoami =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.auth.whoami (error, username) ->
|
||||
return done(error) if error?
|
||||
|
||||
if not username?
|
||||
return done(new Error('Username not found'))
|
||||
|
||||
console.log(username)
|
||||
return done()
|
||||
Promise.props
|
||||
username: resin.auth.whoami()
|
||||
email: resin.auth.getEmail()
|
||||
.then (results) ->
|
||||
console.log visuals.table.vertical results, [
|
||||
'$account information$'
|
||||
'username'
|
||||
'email'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
@ -16,6 +16,18 @@ exports.application = _.defaults
|
||||
required: 'You have to specify an application'
|
||||
, exports.optionalApplication
|
||||
|
||||
exports.optionalDevice =
|
||||
signature: 'device'
|
||||
parameter: 'device'
|
||||
description: 'device name'
|
||||
alias: 'd'
|
||||
|
||||
exports.booleanDevice =
|
||||
signature: 'device'
|
||||
description: 'device name'
|
||||
boolean: true
|
||||
alias: 'd'
|
||||
|
||||
exports.network =
|
||||
signature: 'network'
|
||||
parameter: 'network'
|
||||
|
@ -1,17 +1,17 @@
|
||||
capitano = require('capitano')
|
||||
_ = require('lodash-contrib')
|
||||
path = require('path')
|
||||
async = require('async')
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
vcs = require('resin-vcs')
|
||||
tmp = require('tmp')
|
||||
|
||||
# Cleanup the temporary files even when an uncaught exception occurs
|
||||
form = require('resin-cli-form')
|
||||
events = require('resin-cli-events')
|
||||
rimraf = Promise.promisify(require('rimraf'))
|
||||
patterns = require('../utils/patterns')
|
||||
helpers = require('../utils/helpers')
|
||||
tmp = Promise.promisifyAll(require('tmp'))
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
commandOptions = require('./command-options')
|
||||
osAction = require('./os')
|
||||
|
||||
exports.list =
|
||||
signature: 'devices'
|
||||
@ -30,16 +30,15 @@ exports.list =
|
||||
'''
|
||||
options: [ commandOptions.optionalApplication ]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise.try ->
|
||||
if options.application?
|
||||
return resin.models.device.getAllByApplication(options.application)
|
||||
return resin.models.device.getAll()
|
||||
|
||||
if options.application?
|
||||
getFunction = _.partial(resin.models.device.getAllByApplication, options.application)
|
||||
else
|
||||
getFunction = resin.models.device.getAll
|
||||
|
||||
getFunction (error, devices) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.horizontal devices, [
|
||||
.tap (devices) ->
|
||||
console.log visuals.table.horizontal devices, [
|
||||
'id'
|
||||
'name'
|
||||
'device_type'
|
||||
@ -48,26 +47,31 @@ exports.list =
|
||||
'status'
|
||||
'last_seen'
|
||||
]
|
||||
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.info =
|
||||
signature: 'device <name>'
|
||||
signature: 'device <uuid>'
|
||||
description: 'list a single device'
|
||||
help: '''
|
||||
Use this command to show information about a single device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device MyDevice
|
||||
$ resin device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.get params.name, (error, device) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.vertical device, [
|
||||
resin.models.device.get(params.uuid).then (device) ->
|
||||
|
||||
# TODO: We should outsource this logic and probably
|
||||
# other last_seen edge cases to either Resin CLI Visuals
|
||||
# or have it parsed appropriately in the SDK.
|
||||
device.last_seen ?= 'Not seen'
|
||||
|
||||
console.log visuals.table.vertical device, [
|
||||
"$#{device.name}$"
|
||||
'id'
|
||||
'name'
|
||||
'device_type'
|
||||
'is_online'
|
||||
'ip_address'
|
||||
@ -80,11 +84,39 @@ exports.info =
|
||||
'is_web_accessible'
|
||||
'note'
|
||||
]
|
||||
events.send('device.open', device: device.uuid)
|
||||
.nodeify(done)
|
||||
|
||||
return done()
|
||||
exports.register =
|
||||
signature: 'device register <application>'
|
||||
description: 'register a device'
|
||||
help: '''
|
||||
Use this command to register a device to an application.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device register MyApp
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
signature: 'uuid'
|
||||
description: 'custom uuid'
|
||||
parameter: 'uuid'
|
||||
alias: 'u'
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.get(params.application).then (application) ->
|
||||
|
||||
Promise.try ->
|
||||
return options.uuid or resin.models.device.generateUUID()
|
||||
.then (uuid) ->
|
||||
console.info("Registering to #{application.app_name}: #{uuid}")
|
||||
return resin.models.device.register(application.app_name, uuid)
|
||||
.get('uuid')
|
||||
.nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'device rm <name>'
|
||||
signature: 'device rm <uuid>'
|
||||
description: 'remove a device'
|
||||
help: '''
|
||||
Use this command to remove a device from resin.io.
|
||||
@ -94,15 +126,17 @@ exports.remove =
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rm MyDevice
|
||||
$ resin device rm MyDevice --yes
|
||||
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --yes
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'device', options.yes, (callback) ->
|
||||
resin.models.device.remove(params.name, callback)
|
||||
, done
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the device?').then ->
|
||||
resin.models.device.remove(params.uuid)
|
||||
.tap ->
|
||||
events.send('device.delete', device: params.uuid)
|
||||
.nodeify(done)
|
||||
|
||||
exports.identify =
|
||||
signature: 'device identify <uuid>'
|
||||
@ -118,10 +152,10 @@ exports.identify =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.identify(params.uuid, done)
|
||||
resin.models.device.identify(params.uuid).nodeify(done)
|
||||
|
||||
exports.rename =
|
||||
signature: 'device rename <name> [newName]'
|
||||
signature: 'device rename <uuid> [newName]'
|
||||
description: 'rename a resin device'
|
||||
help: '''
|
||||
Use this command to rename a device.
|
||||
@ -130,163 +164,73 @@ exports.rename =
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device rename MyDevice MyPi
|
||||
$ resin device rename MyDevice
|
||||
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 MyPi
|
||||
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
async.waterfall [
|
||||
Promise.try ->
|
||||
return params.newName if not _.isEmpty(params.newName)
|
||||
|
||||
(callback) ->
|
||||
if not _.isEmpty(params.newName)
|
||||
return callback(null, params.newName)
|
||||
visuals.widgets.ask('How do you want to name this device?', null, callback)
|
||||
form.ask
|
||||
message: 'How do you want to name this device?'
|
||||
type: 'input'
|
||||
|
||||
(newName, callback) ->
|
||||
resin.models.device.rename(params.name, newName, callback)
|
||||
|
||||
], done
|
||||
|
||||
exports.supported =
|
||||
signature: 'devices supported'
|
||||
description: 'list all supported devices'
|
||||
help: '''
|
||||
Use this command to get the list of all supported devices
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin devices supported
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.device.getSupportedDeviceTypes (error, devices) ->
|
||||
return done(error) if error?
|
||||
_.each(devices, _.unary(console.log))
|
||||
done()
|
||||
|
||||
exports.await =
|
||||
signature: 'device await <name>'
|
||||
description: 'await for a device to become online'
|
||||
help: '''
|
||||
Use this command to await for a device to become online.
|
||||
|
||||
The process will exit when the device becomes online.
|
||||
|
||||
Notice that there is no time limit for this command, so it might run forever.
|
||||
|
||||
You can configure the poll interval with the --interval option (defaults to 3000ms).
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin device await MyDevice
|
||||
$ resin device await MyDevice --interval 1000
|
||||
'''
|
||||
options: [
|
||||
signature: 'interval'
|
||||
parameter: 'interval'
|
||||
description: 'poll interval'
|
||||
alias: 'i'
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
options.interval ?= 3000
|
||||
|
||||
poll = ->
|
||||
resin.models.device.isOnline params.name, (error, isOnline) ->
|
||||
return done(error) if error?
|
||||
|
||||
if isOnline
|
||||
console.info("Device became online: #{params.name}")
|
||||
return done()
|
||||
else
|
||||
console.info("Polling device network status: #{params.name}")
|
||||
setTimeout(poll, options.interval)
|
||||
|
||||
poll()
|
||||
.then(_.partial(resin.models.device.rename, params.uuid))
|
||||
.tap ->
|
||||
events.send('device.rename', device: params.uuid)
|
||||
.nodeify(done)
|
||||
|
||||
exports.init =
|
||||
signature: 'device init [device]'
|
||||
signature: 'device init'
|
||||
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.
|
||||
|
||||
Note that this command requires admin privileges.
|
||||
|
||||
If `device` is omitted, you will be prompted to select a device interactively.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
Notice this command may ask for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
You can quiet the progress bar by passing the `--quiet` boolean option.
|
||||
|
||||
You may have to unmount the device before attempting this operation.
|
||||
|
||||
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:
|
||||
|
||||
$ resin device init
|
||||
$ 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: [
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.network
|
||||
commandOptions.wifiSsid
|
||||
commandOptions.wifiKey
|
||||
commandOptions.yes
|
||||
{
|
||||
signature: 'advanced'
|
||||
description: 'enable advanced configuration'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise.try ->
|
||||
return options.application if options.application?
|
||||
return patterns.selectApplication()
|
||||
.then(resin.models.application.get)
|
||||
.then (application) ->
|
||||
|
||||
async.waterfall([
|
||||
download = ->
|
||||
tmp.tmpNameAsync().then (temporalPath) ->
|
||||
capitano.runAsync("os download #{application.device_type} --output #{temporalPath}")
|
||||
.disposer (temporalPath) ->
|
||||
return rimraf(temporalPath)
|
||||
|
||||
(callback) ->
|
||||
return callback(null, options.application) if options.application?
|
||||
vcs.getApplicationName(process.cwd(), callback)
|
||||
Promise.using download(), (temporalPath) ->
|
||||
capitano.runAsync("device register #{application.app_name}")
|
||||
.then(resin.models.device.get)
|
||||
.tap (device) ->
|
||||
configure = "os configure #{temporalPath} #{device.uuid}"
|
||||
configure += ' --advanced' if options.advanced
|
||||
capitano.runAsync(configure).then ->
|
||||
|
||||
(applicationName, callback) ->
|
||||
params.name = applicationName
|
||||
return callback(null, params.device) if params.device?
|
||||
visuals.patterns.selectDrive(callback)
|
||||
helpers.sudo([ 'os', 'initialize', temporalPath, '--type', application.device_type ])
|
||||
.then (device) ->
|
||||
console.log('Done')
|
||||
return device.uuid
|
||||
|
||||
(device, callback) ->
|
||||
params.device = device
|
||||
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
|
||||
visuals.patterns.confirm(options.yes, message, callback)
|
||||
|
||||
(confirmed, callback) ->
|
||||
return done() if not confirmed
|
||||
options.yes = confirmed
|
||||
|
||||
tmp.file
|
||||
prefix: 'resin-image-'
|
||||
postfix: '.img'
|
||||
, callback
|
||||
|
||||
(tmpPath, tmpFd, cleanupCallback, callback) ->
|
||||
options.output = tmpPath
|
||||
|
||||
# TODO: Figure out how to make use of capitano.run()
|
||||
# here given the complexity of converting network
|
||||
# params object to string options
|
||||
osAction.download.action params, options, (error, outputFile) ->
|
||||
return callback(error) if error?
|
||||
return callback(null, outputFile, cleanupCallback)
|
||||
|
||||
(outputFile, cleanupCallback, callback) ->
|
||||
capitano.run "os install #{outputFile} #{params.device}", (error) ->
|
||||
return callback(error) if error?
|
||||
cleanupCallback()
|
||||
return callback()
|
||||
|
||||
], done)
|
||||
.nodeify(done)
|
||||
|
@ -1,32 +0,0 @@
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
visuals = require('resin-cli-visuals')
|
||||
drivelist = require('drivelist')
|
||||
|
||||
exports.list =
|
||||
signature: 'drives'
|
||||
description: 'list available drives'
|
||||
help: '''
|
||||
Use this command to list all drives that are connected to your machine.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin drives
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
drivelist.list (error, drives) ->
|
||||
return done(error) if error?
|
||||
|
||||
async.reject drives, drivelist.isSystem, (removableDrives) ->
|
||||
|
||||
if _.isEmpty(removableDrives)
|
||||
return done(new Error('No removable devices available'))
|
||||
|
||||
console.log visuals.widgets.table.horizontal removableDrives, [
|
||||
'device'
|
||||
'description'
|
||||
'size'
|
||||
]
|
||||
|
||||
return done()
|
@ -1,26 +1,31 @@
|
||||
_ = require('lodash-contrib')
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
events = require('resin-cli-events')
|
||||
commandOptions = require('./command-options')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
exports.list =
|
||||
signature: 'envs'
|
||||
description: 'list all environment variables'
|
||||
help: '''
|
||||
Use this command to list all environment variables for a particular application.
|
||||
Notice we will support per-device environment variables soon.
|
||||
Use this command to list all environment variables for
|
||||
a particular application or device.
|
||||
|
||||
This command lists all custom environment variables set on the devices running
|
||||
the application. If you want to see all environment variables, including private
|
||||
This command lists all custom environment variables.
|
||||
If you want to see all environment variables, including private
|
||||
ones used by resin, use the verbose option.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin envs --application 91
|
||||
$ resin envs --application 91 --verbose
|
||||
$ resin envs --application MyApp
|
||||
$ resin envs --application MyApp --verbose
|
||||
$ resin envs --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
'''
|
||||
options: [
|
||||
commandOptions.application
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
|
||||
{
|
||||
signature: 'verbose'
|
||||
@ -31,19 +36,27 @@ exports.list =
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.environmentVariables.getAllByApplication options.application, (error, environmentVariables) ->
|
||||
return done(error) if error?
|
||||
Promise.try ->
|
||||
if options.application?
|
||||
return resin.models.environmentVariables.getAllByApplication(options.application)
|
||||
else if options.device?
|
||||
return resin.models.environmentVariables.device.getAll(options.device)
|
||||
else
|
||||
throw new Error('You must specify an application or device')
|
||||
|
||||
.tap (environmentVariables) ->
|
||||
if _.isEmpty(environmentVariables)
|
||||
throw new Error('No environment variables found')
|
||||
if not options.verbose
|
||||
environmentVariables = _.reject(environmentVariables, resin.models.environmentVariables.isSystemVariable)
|
||||
isSystemVariable = resin.models.environmentVariables.isSystemVariable
|
||||
environmentVariables = _.reject(environmentVariables, isSystemVariable)
|
||||
|
||||
console.log visuals.widgets.table.horizontal environmentVariables, [
|
||||
console.log visuals.table.horizontal environmentVariables, [
|
||||
'id'
|
||||
'name'
|
||||
'value'
|
||||
]
|
||||
|
||||
return done()
|
||||
.nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'env rm <id>'
|
||||
@ -56,17 +69,28 @@ exports.remove =
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
If you want to eliminate a device environment variable, pass the `--device` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env rm 215
|
||||
$ resin env rm 215 --yes
|
||||
$ resin env rm 215 --device
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
options: [
|
||||
commandOptions.yes
|
||||
commandOptions.booleanDevice
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'environment variable', options.yes, (callback) ->
|
||||
resin.models.environmentVariables.remove(params.id, callback)
|
||||
, done
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then ->
|
||||
if options.device
|
||||
resin.models.environmentVariables.device.remove(params.id)
|
||||
events.send('deviceEnvironmentVariable.delete', id: params.id)
|
||||
else
|
||||
resin.models.environmentVariables.remove(params.id)
|
||||
events.send('environmentVariable.delete', id: params.id)
|
||||
.nodeify(done)
|
||||
|
||||
exports.add =
|
||||
signature: 'env add <key> [value]'
|
||||
@ -74,31 +98,46 @@ exports.add =
|
||||
help: '''
|
||||
Use this command to add an enviroment variable to an application.
|
||||
|
||||
You need to pass the `--application` option.
|
||||
|
||||
If value is omitted, the tool will attempt to use the variable's value
|
||||
as defined in your host machine.
|
||||
|
||||
Use the `--device` option if you want to assign the environment variable
|
||||
to a specific device.
|
||||
|
||||
If the value is grabbed from the environment, a warning message will be printed.
|
||||
Use `--quiet` to remove it.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env add EDITOR vim -a 91
|
||||
$ resin env add TERM -a 91
|
||||
$ resin env add EDITOR vim --application MyApp
|
||||
$ resin env add TERM --application MyApp
|
||||
$ resin env add EDITOR vim --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
'''
|
||||
options: [ commandOptions.application ]
|
||||
options: [
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
if not params.value?
|
||||
params.value = process.env[params.key]
|
||||
|
||||
Promise.try ->
|
||||
if not params.value?
|
||||
return done(new Error("Environment value not found for key: #{params.key}"))
|
||||
else
|
||||
console.info("Warning: using #{params.key}=#{params.value} from host environment")
|
||||
params.value = process.env[params.key]
|
||||
|
||||
resin.models.environmentVariables.create(options.application, params.key, params.value, done)
|
||||
if not params.value?
|
||||
throw new Error("Environment value not found for key: #{params.key}")
|
||||
else
|
||||
console.info("Warning: using #{params.key}=#{params.value} from host environment")
|
||||
|
||||
if options.application?
|
||||
resin.models.environmentVariables.create(options.application, params.key, params.value).then ->
|
||||
resin.models.application.get(options.application).then (application) ->
|
||||
events.send('environmentVariable.create', application: application.id)
|
||||
else if options.device?
|
||||
resin.models.environmentVariables.device.create(options.device, params.key, params.value).then ->
|
||||
events.send('deviceEnvironmentVariable.create', device: options.device)
|
||||
else
|
||||
throw new Error('You must specify an application or device')
|
||||
.nodeify(done)
|
||||
|
||||
exports.rename =
|
||||
signature: 'env rename <id> <value>'
|
||||
@ -106,10 +145,21 @@ exports.rename =
|
||||
help: '''
|
||||
Use this command to rename an enviroment variable from an application.
|
||||
|
||||
Pass the `--device` boolean option if you want to rename a device environment variable.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin env rename 376 emacs
|
||||
$ resin env rename 376 emacs --device
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [ commandOptions.booleanDevice ]
|
||||
action: (params, options, done) ->
|
||||
resin.models.environmentVariables.update(params.id, params.value, done)
|
||||
Promise.try ->
|
||||
if options.device
|
||||
resin.models.environmentVariables.device.update(params.id, params.value).then ->
|
||||
events.send('deviceEnvironmentVariable.edit', id: params.id)
|
||||
else
|
||||
resin.models.environmentVariables.update(params.id, params.value).then ->
|
||||
events.send('environmentVariable.edit', id: params.id)
|
||||
.nodeify(done)
|
||||
|
@ -1,96 +0,0 @@
|
||||
mkdirp = require('mkdirp')
|
||||
async = require('async')
|
||||
fs = require('fs')
|
||||
path = require('path')
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
visuals = require('resin-cli-visuals')
|
||||
vcs = require('resin-vcs')
|
||||
examplesData = require('../data/examples.json')
|
||||
|
||||
exports.list =
|
||||
signature: 'examples'
|
||||
description: 'list all example applications'
|
||||
help: '''
|
||||
Use this command to list available example applications from resin.io
|
||||
|
||||
Example:
|
||||
|
||||
$ resin examples
|
||||
'''
|
||||
permission: 'user'
|
||||
action: ->
|
||||
examplesData = _.map examplesData, (example, index) ->
|
||||
example.id = index + 1
|
||||
return example
|
||||
|
||||
examplesData = _.map examplesData, (example) ->
|
||||
example.author ?= 'Unknown'
|
||||
return example
|
||||
|
||||
console.log visuals.widgets.table.horizontal examplesData, [
|
||||
'id'
|
||||
'display_name'
|
||||
'repository'
|
||||
'author'
|
||||
]
|
||||
|
||||
exports.info =
|
||||
signature: 'example <id>'
|
||||
description: 'list a single example application'
|
||||
help: '''
|
||||
Use this command to show information of a single example application
|
||||
|
||||
Example:
|
||||
|
||||
$ resin example 3
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
id = params.id - 1
|
||||
example = examplesData[id]
|
||||
|
||||
if not example?
|
||||
return done(new Error("Unknown example: #{id}"))
|
||||
|
||||
example.id = id
|
||||
example.author ?= 'Unknown'
|
||||
|
||||
console.log visuals.widgets.table.vertical example, [
|
||||
'id'
|
||||
'display_name'
|
||||
'description'
|
||||
'author'
|
||||
'repository'
|
||||
]
|
||||
|
||||
return done()
|
||||
|
||||
exports.clone =
|
||||
signature: 'example clone <id>'
|
||||
description: 'clone an example application'
|
||||
help: '''
|
||||
Use this command to clone an example application to the current directory
|
||||
|
||||
This command outputs information about the cloning process.
|
||||
Use `--quiet` to remove that output.
|
||||
|
||||
Example:
|
||||
|
||||
$ resin example clone 3
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
example = examplesData[params.id - 1]
|
||||
|
||||
if not example?
|
||||
return done(new Error("Unknown example: #{id}"))
|
||||
|
||||
currentDirectory = process.cwd()
|
||||
destination = path.join(currentDirectory, example.name)
|
||||
|
||||
mkdirp destination, (error) ->
|
||||
return done(error) if error?
|
||||
console.info("Cloning #{example.display_name} to #{destination}")
|
||||
vcs.clone(example.repository, destination, done)
|
||||
return done()
|
@ -1,93 +1,73 @@
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
resin = require('resin-sdk')
|
||||
capitano = require('capitano')
|
||||
columnify = require('columnify')
|
||||
|
||||
# TODO: Refactor this terrible mess
|
||||
parse = (object) ->
|
||||
return _.object _.map object, (item) ->
|
||||
|
||||
PADDING_INITIAL = ' '
|
||||
PADDING_MIDDLE = '\t'
|
||||
# Hacky way to determine if an object is
|
||||
# a function or a command
|
||||
if item.alias?
|
||||
signature = item.toString()
|
||||
else
|
||||
signature = item.signature.toString()
|
||||
|
||||
getFieldMaxLength = (array, field) ->
|
||||
return _.max _.map array, (item) ->
|
||||
return item[field].toString().length
|
||||
return [
|
||||
signature
|
||||
item.description
|
||||
]
|
||||
|
||||
buildHelpString = (firstColumn, secondColumn) ->
|
||||
result = "#{PADDING_INITIAL}#{firstColumn}"
|
||||
result += "#{PADDING_MIDDLE}#{secondColumn}"
|
||||
return result
|
||||
indent = (text) ->
|
||||
text = _.map _.str.lines(text), (line) ->
|
||||
return ' ' + line
|
||||
return text.join('\n')
|
||||
|
||||
addOptionPrefix = (option) ->
|
||||
return if option.length <= 0
|
||||
if option.length is 1
|
||||
return "-#{option}"
|
||||
else
|
||||
return "--#{option}"
|
||||
print = (data) ->
|
||||
console.log indent columnify data,
|
||||
showHeaders: false
|
||||
minWidth: 35
|
||||
|
||||
addAlias = (alias) ->
|
||||
return ", #{addOptionPrefix(alias)}"
|
||||
|
||||
buildOptionSignatureHelp = (option) ->
|
||||
result = addOptionPrefix(option.signature.toString())
|
||||
|
||||
if _.isString(option.alias)
|
||||
result += addAlias(option.alias)
|
||||
else if _.isArray(option.alias)
|
||||
for alias in option.alias
|
||||
result += addAlias(alias)
|
||||
|
||||
if option.parameter?
|
||||
result += " <#{option.parameter}>"
|
||||
|
||||
return result
|
||||
|
||||
getCommandHelp = (command) ->
|
||||
maxSignatureLength = getFieldMaxLength(capitano.state.commands, 'signature')
|
||||
commandSignature = _.str.rpad(command.signature.toString(), maxSignatureLength, ' ')
|
||||
return buildHelpString(commandSignature, command.description)
|
||||
|
||||
getOptionsParsedSignatures = (optionsHelp) ->
|
||||
maxLength = _.max _.map optionsHelp, (signature) ->
|
||||
return signature.length
|
||||
|
||||
return _.map optionsHelp, (signature) ->
|
||||
return _.str.rpad(signature, maxLength, ' ')
|
||||
|
||||
getOptionHelp = (option, maxLength) ->
|
||||
result = PADDING_INITIAL
|
||||
result += _.str.rpad(option.signature, maxLength, ' ')
|
||||
result += PADDING_MIDDLE
|
||||
result += option.description
|
||||
return result
|
||||
|
||||
general = ->
|
||||
general = (params, options, done) ->
|
||||
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
|
||||
console.log('Commands:\n')
|
||||
console.log('Primary commands:\n')
|
||||
|
||||
for command in capitano.state.commands
|
||||
continue if command.isWildcard()
|
||||
console.log(getCommandHelp(command))
|
||||
# We do not want the wildcard command
|
||||
# to be printed in the help screen.
|
||||
commands = _.reject capitano.state.commands, (command) ->
|
||||
return command.isWildcard()
|
||||
|
||||
console.log('\nGlobal Options:\n')
|
||||
groupedCommands = _.groupBy commands, (command) ->
|
||||
if command.plugin
|
||||
return 'plugins'
|
||||
else if command.primary
|
||||
return 'primary'
|
||||
return 'secondary'
|
||||
|
||||
options = _.map capitano.state.globalOptions, (option) ->
|
||||
option.signature = buildOptionSignatureHelp(option)
|
||||
return option
|
||||
print(parse(groupedCommands.primary))
|
||||
|
||||
optionSignatureMaxLength = _.max _.map options, (option) ->
|
||||
return option.signature.length
|
||||
if options.verbose
|
||||
if not _.isEmpty(groupedCommands.plugins)
|
||||
console.log('\nInstalled plugins:\n')
|
||||
print(parse(groupedCommands.plugins))
|
||||
|
||||
for option in options
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength))
|
||||
console.log('\nAdditional commands:\n')
|
||||
print(parse(groupedCommands.secondary))
|
||||
else
|
||||
console.log('\nRun `resin help --verbose` to list additional commands')
|
||||
|
||||
console.log()
|
||||
if not _.isEmpty(capitano.state.globalOptions)
|
||||
console.log('\nGlobal Options:\n')
|
||||
print(parse(capitano.state.globalOptions))
|
||||
|
||||
return done()
|
||||
|
||||
command = (params, options, done) ->
|
||||
capitano.state.getMatchCommand params.command, (error, command) ->
|
||||
return done(error) if error?
|
||||
|
||||
if not command? or command.isWildcard()
|
||||
return capitano.defaults.actions.commandNotFound(params.command)
|
||||
return done(new Error("Command not found: #{params.command}"))
|
||||
|
||||
console.log("Usage: #{command.signature}")
|
||||
|
||||
@ -98,18 +78,7 @@ command = (params, options, done) ->
|
||||
|
||||
if not _.isEmpty(command.options)
|
||||
console.log('\nOptions:\n')
|
||||
|
||||
options = _.map command.options, (option) ->
|
||||
option.signature = buildOptionSignatureHelp(option)
|
||||
return option
|
||||
|
||||
optionSignatureMaxLength = _.max _.map options, (option) ->
|
||||
return option.signature.toString().length
|
||||
|
||||
for option in options
|
||||
console.log(getOptionHelp(option, optionSignatureMaxLength))
|
||||
|
||||
console.log()
|
||||
print(parse(command.options))
|
||||
|
||||
return done()
|
||||
|
||||
@ -124,6 +93,13 @@ exports.help =
|
||||
$ resin help apps
|
||||
$ resin help os download
|
||||
'''
|
||||
primary: true
|
||||
options: [
|
||||
signature: 'verbose'
|
||||
description: 'show additional commands'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
if params.command?
|
||||
command(params, options, done)
|
||||
|
@ -1,16 +1,12 @@
|
||||
module.exports =
|
||||
wizard: require('./wizard')
|
||||
app: require('./app')
|
||||
info: require('./info')
|
||||
auth: require('./auth')
|
||||
drive: require('./drive')
|
||||
device: require('./device')
|
||||
env: require('./environment-variables')
|
||||
keys: require('./keys')
|
||||
logs: require('./logs')
|
||||
notes: require('./notes')
|
||||
preferences: require('./preferences')
|
||||
os: require('./os')
|
||||
help: require('./help')
|
||||
examples: require('./examples')
|
||||
plugin: require('./plugin')
|
||||
update: require('./update')
|
||||
os: require('./os')
|
||||
|
@ -1,11 +1,12 @@
|
||||
Promise = require('bluebird')
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
async = require('async')
|
||||
fs = require('fs')
|
||||
resin = require('resin-sdk')
|
||||
capitano = require('capitano')
|
||||
visuals = require('resin-cli-visuals')
|
||||
events = require('resin-cli-events')
|
||||
commandOptions = require('./command-options')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
exports.list =
|
||||
signature: 'keys'
|
||||
@ -19,12 +20,12 @@ exports.list =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.key.getAll (error, keys) ->
|
||||
return done(error) if error?
|
||||
console.log visuals.widgets.table.horizontal keys, [ 'id', 'title' ]
|
||||
return done()
|
||||
|
||||
SSH_KEY_WIDTH = 43
|
||||
resin.models.key.getAll().then (keys) ->
|
||||
console.log visuals.table.horizontal keys, [
|
||||
'id'
|
||||
'title'
|
||||
]
|
||||
.nodeify(done)
|
||||
|
||||
exports.info =
|
||||
signature: 'key <id>'
|
||||
@ -38,13 +39,17 @@ exports.info =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
resin.models.key.get params.id, (error, key) ->
|
||||
return done(error) if error?
|
||||
resin.models.key.get(params.id).then (key) ->
|
||||
console.log visuals.table.vertical key, [
|
||||
'id'
|
||||
'title'
|
||||
]
|
||||
|
||||
key.public_key = '\n' + visuals.helpers.chop(key.public_key, SSH_KEY_WIDTH)
|
||||
|
||||
console.log(visuals.widgets.table.vertical(key, [ 'id', 'title', 'public_key' ]))
|
||||
return done()
|
||||
# Since the public key string is long, it might
|
||||
# wrap to lines below, causing the table layout to break.
|
||||
# See https://github.com/resin-io/resin-cli/issues/151
|
||||
console.log('\n' + key.public_key)
|
||||
.nodeify(done)
|
||||
|
||||
exports.remove =
|
||||
signature: 'key rm <id>'
|
||||
@ -63,9 +68,11 @@ exports.remove =
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'key', options.yes, (callback) ->
|
||||
resin.models.key.remove(params.id, callback)
|
||||
, done
|
||||
patterns.confirm(options.yes, 'Are you sure you want to delete the key?').then ->
|
||||
resin.models.key.remove(params.id)
|
||||
.tap ->
|
||||
events.send('publicKey.delete', id: params.id)
|
||||
.nodeify(done)
|
||||
|
||||
exports.add =
|
||||
signature: 'key add <name> [path]'
|
||||
@ -83,16 +90,14 @@ exports.add =
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
async.waterfall [
|
||||
Promise.try ->
|
||||
return fs.readFileAsync(params.path, encoding: 'utf8') if params.path?
|
||||
|
||||
(callback) ->
|
||||
if params.path?
|
||||
fs.readFile(params.path, encoding: 'utf8', callback)
|
||||
else
|
||||
capitano.utils.getStdin (data) ->
|
||||
return callback(null, data)
|
||||
Promise.fromNode (callback) ->
|
||||
capitano.utils.getStdin (data) ->
|
||||
return callback(null, data)
|
||||
|
||||
(key, callback) ->
|
||||
resin.models.key.create(params.name, key, callback)
|
||||
|
||||
], done
|
||||
.then(_.partial(resin.models.key.create, params.name))
|
||||
.tap ->
|
||||
events.send('publicKey.create')
|
||||
.nodeify(done)
|
||||
|
@ -1,9 +1,7 @@
|
||||
_ = require('lodash')
|
||||
resin = require('resin-sdk')
|
||||
|
||||
LOGS_HISTORY_COUNT = 200
|
||||
|
||||
exports.logs =
|
||||
module.exports =
|
||||
signature: 'logs <uuid>'
|
||||
description: 'show device logs'
|
||||
help: '''
|
||||
@ -11,9 +9,6 @@ exports.logs =
|
||||
|
||||
By default, the command prints all log messages and exit.
|
||||
|
||||
To limit the output to the n last lines, use the `--num` option along with a number.
|
||||
This is similar to doing `resin logs <uuid> | tail -n X`.
|
||||
|
||||
To continuously stream output, and see new logs in real time, use the `--tail` option.
|
||||
|
||||
Note that for now you need to provide the whole UUID for this command to work correctly.
|
||||
@ -23,16 +18,9 @@ exports.logs =
|
||||
Examples:
|
||||
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --num 20
|
||||
$ resin logs 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828 --tail
|
||||
'''
|
||||
options: [
|
||||
{
|
||||
signature: 'num'
|
||||
parameter: 'num'
|
||||
description: 'number of lines to display'
|
||||
alias: 'n'
|
||||
}
|
||||
{
|
||||
signature: 'tail'
|
||||
description: 'continuously stream output'
|
||||
@ -41,15 +29,22 @@ exports.logs =
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
resin.logs.subscribe params.uuid, {
|
||||
history: options.num or LOGS_HISTORY_COUNT
|
||||
tail: options.tail
|
||||
}, (error, message) ->
|
||||
return done(error) if error?
|
||||
if _.isArray(message)
|
||||
_.each message, (line) ->
|
||||
promise = resin.logs.history(params.uuid).each (line) ->
|
||||
console.log(line.message)
|
||||
|
||||
if not options.tail
|
||||
|
||||
# PubNub keeps the process alive after a history query.
|
||||
# Until this is fixed, we force the process to exit.
|
||||
# This of course prevents this command to be used programatically
|
||||
return promise.catch(done).finally ->
|
||||
process.exit(0)
|
||||
|
||||
promise.then ->
|
||||
resin.logs.subscribe(params.uuid).then (logs) ->
|
||||
logs.on 'line', (line) ->
|
||||
console.log(line.message)
|
||||
else
|
||||
console.log(message.message)
|
||||
return done()
|
||||
logs.on('error', done)
|
||||
.catch(done)
|
||||
|
@ -1,5 +1,5 @@
|
||||
Promise = require('bluebird')
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
resin = require('resin-sdk')
|
||||
|
||||
exports.set =
|
||||
@ -10,24 +10,25 @@ exports.set =
|
||||
|
||||
If note command isn't passed, the tool attempts to read from `stdin`.
|
||||
|
||||
To view the notes, use $ resin device <name>.
|
||||
To view the notes, use $ resin device <uuid>.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin note "My useful note" --device MyDevice
|
||||
$ cat note.txt | resin note --device MyDevice
|
||||
$ resin note "My useful note" --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
$ cat note.txt | resin note --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
'''
|
||||
options: [
|
||||
signature: 'device'
|
||||
parameter: 'device'
|
||||
description: 'device name'
|
||||
description: 'device uuid'
|
||||
alias: [ 'd', 'dev' ]
|
||||
required: 'You have to specify a device'
|
||||
]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise.try ->
|
||||
if _.isEmpty(params.note)
|
||||
throw new Error('Missing note content')
|
||||
|
||||
if _.isEmpty(params.note)
|
||||
return done(new Error('Missing note content'))
|
||||
|
||||
resin.models.device.note(options.device, params.note, done)
|
||||
resin.models.device.note(options.device, params.note)
|
||||
.nodeify(done)
|
||||
|
@ -1,171 +1,164 @@
|
||||
capitano = require('capitano')
|
||||
_ = require('lodash-contrib')
|
||||
os = require('os')
|
||||
async = require('async')
|
||||
path = require('path')
|
||||
mkdirp = require('mkdirp')
|
||||
fs = require('fs')
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
umount = Promise.promisifyAll(require('umount'))
|
||||
unzip = require('unzip2')
|
||||
rindle = require('rindle')
|
||||
resin = require('resin-sdk')
|
||||
image = require('resin-image')
|
||||
manager = require('resin-image-manager')
|
||||
visuals = require('resin-cli-visuals')
|
||||
form = require('resin-cli-form')
|
||||
init = require('resin-device-init')
|
||||
commandOptions = require('./command-options')
|
||||
npm = require('../npm')
|
||||
packageJSON = require('../../package.json')
|
||||
elevate = require('../elevate')
|
||||
helpers = require('../utils/helpers')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
exports.download =
|
||||
signature: 'os download <name>'
|
||||
description: 'download device OS'
|
||||
signature: 'os download <type>'
|
||||
description: 'download an unconfigured os image'
|
||||
help: '''
|
||||
Use this command to download the device OS configured to a specific network.
|
||||
|
||||
Ethernet:
|
||||
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
|
||||
|
||||
Wifi:
|
||||
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
|
||||
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
|
||||
|
||||
Alternatively, you can omit all kind of network configuration options to configure interactively.
|
||||
|
||||
You have to specify an output location with the `--output` option.
|
||||
Use this command to download an unconfigured os image for a certain device type.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os download MyApp --output ~/MyResinOS.zip
|
||||
$ resin os download MyApp --network ethernet --output ~/MyResinOS.zip
|
||||
$ resin os download MyApp --network wifi --ssid MyNetwork --key secreykey123 --output ~/MyResinOS.zip
|
||||
$ resin os download MyApp --network ethernet --output ~/MyResinOS.zip
|
||||
$ resin os download parallella -o ../foo/bar/parallella.img
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
commandOptions.network
|
||||
commandOptions.wifiSsid
|
||||
commandOptions.wifiKey
|
||||
signature: 'output'
|
||||
description: 'output path'
|
||||
parameter: 'output'
|
||||
alias: 'o'
|
||||
required: 'You have to specify an output location'
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
console.info("Getting device operating system for #{params.type}")
|
||||
|
||||
manager.get(params.type).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()
|
||||
|
||||
# We completely rely on the `mime` custom property
|
||||
# to make this decision.
|
||||
# The actual stream should be checked instead.
|
||||
if stream.mime is 'application/zip'
|
||||
output = unzip.Extract(path: options.output)
|
||||
else
|
||||
output = fs.createWriteStream(options.output)
|
||||
|
||||
return rindle.wait(stream.pipe(output)).return(options.output)
|
||||
.tap (output) ->
|
||||
console.info("The image was downloaded to #{output}")
|
||||
.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 rindle.wait(step)
|
||||
|
||||
exports.configure =
|
||||
signature: 'os configure <image> <uuid>'
|
||||
description: 'configure an os image'
|
||||
help: '''
|
||||
Use this command to configure a previously download operating system image with a device.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os configure ../path/rpi.img 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
signature: 'advanced'
|
||||
description: 'show advanced commands'
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
console.info('Configuring operating system image')
|
||||
resin.models.device.get(params.uuid)
|
||||
.get('device_type')
|
||||
.then(resin.models.device.getManifestBySlug)
|
||||
.get('options')
|
||||
.then (questions) ->
|
||||
|
||||
if not options.advanced
|
||||
advancedGroup = _.findWhere questions,
|
||||
name: 'advanced'
|
||||
isGroup: true
|
||||
|
||||
if advancedGroup?
|
||||
override = helpers.getGroupDefaults(advancedGroup)
|
||||
|
||||
return form.run(questions, { override })
|
||||
.then (answers) ->
|
||||
init.configure(params.image, params.uuid, answers).then(stepHandler)
|
||||
.nodeify(done)
|
||||
|
||||
exports.initialize =
|
||||
signature: 'os initialize <image>'
|
||||
description: 'initialize an os image'
|
||||
help: '''
|
||||
Use this command to initialize a previously configured operating system image.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
|
||||
'''
|
||||
permission: 'user'
|
||||
options: [
|
||||
commandOptions.yes
|
||||
{
|
||||
signature: 'output'
|
||||
parameter: 'output'
|
||||
description: 'output file'
|
||||
alias: 'o'
|
||||
required: 'You need to specify an output file'
|
||||
signature: 'type'
|
||||
description: 'device type'
|
||||
parameter: 'type'
|
||||
alias: 't'
|
||||
required: 'You have to specify a device type'
|
||||
}
|
||||
{
|
||||
signature: 'drive'
|
||||
description: 'drive'
|
||||
parameter: 'drive'
|
||||
alias: 'd'
|
||||
}
|
||||
]
|
||||
permission: 'user'
|
||||
root: true
|
||||
action: (params, options, done) ->
|
||||
resin.models.application.get params.name, (error, application) ->
|
||||
return done(error) if error?
|
||||
|
||||
osParams =
|
||||
network: options.network
|
||||
wifiSsid: options.ssid
|
||||
wifiKey: options.key
|
||||
appId: application.id
|
||||
|
||||
async.waterfall [
|
||||
|
||||
(callback) ->
|
||||
return callback() if osParams.network?
|
||||
visuals.patterns.selectNetworkParameters (error, parameters) ->
|
||||
return callback(error) if error?
|
||||
_.extend(osParams, parameters)
|
||||
return callback()
|
||||
|
||||
(callback) ->
|
||||
|
||||
# We need to ensure this directory exists
|
||||
mkdirp(path.dirname(options.output), _.unary(callback))
|
||||
|
||||
(callback) ->
|
||||
console.info("Destination file: #{options.output}\n")
|
||||
|
||||
bar = new visuals.widgets.Progress('Downloading Device OS')
|
||||
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)')
|
||||
|
||||
resin.models.os.download osParams, options.output, (error) ->
|
||||
spinner.stop()
|
||||
return callback(error) if error?
|
||||
, (state) ->
|
||||
if state?
|
||||
bar.update(state)
|
||||
else
|
||||
spinner.start()
|
||||
|
||||
], (error) ->
|
||||
return done(error) if error?
|
||||
console.info("\nFinished downloading #{options.output}")
|
||||
return done(null, options.output)
|
||||
|
||||
exports.install =
|
||||
signature: 'os install <image> [device]'
|
||||
description: 'write an operating system image to a device'
|
||||
help: '''
|
||||
Use this command to write an operating system image to a device.
|
||||
|
||||
Note that this command requires admin privileges.
|
||||
|
||||
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 quiet the progress bar by passing the `--quiet` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin os install rpi.iso /dev/disk2
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
|
||||
async.waterfall [
|
||||
|
||||
(callback) ->
|
||||
npm.isUpdated(packageJSON.name, packageJSON.version, callback)
|
||||
|
||||
(isUpdated, callback) ->
|
||||
return callback() if isUpdated
|
||||
|
||||
console.info '''
|
||||
Resin CLI is outdated.
|
||||
|
||||
In order to avoid device compatibility issues, this command
|
||||
requires that you have the Resin CLI updated.
|
||||
|
||||
Updating now...
|
||||
'''
|
||||
|
||||
capitano.run('update', _.unary(callback))
|
||||
|
||||
(callback) ->
|
||||
return callback(null, params.device) if params.device?
|
||||
|
||||
# TODO: See if we can reuse the drives action somehow here
|
||||
visuals.patterns.selectDrive (error, device) ->
|
||||
return callback(error) if error?
|
||||
|
||||
if not device?
|
||||
return callback(new Error('No removable devices available'))
|
||||
|
||||
return callback(null, device)
|
||||
|
||||
(device, callback) ->
|
||||
params.device = device
|
||||
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
|
||||
visuals.patterns.confirm(options.yes, message, callback)
|
||||
|
||||
(confirmed, callback) ->
|
||||
return done() if not confirmed
|
||||
bar = new visuals.widgets.Progress('Writing Device OS')
|
||||
params.progress = _.bind(bar.update, bar)
|
||||
image.write(params, callback)
|
||||
|
||||
], (error) ->
|
||||
return done() if not error?
|
||||
|
||||
if elevate.shouldElevate(error) and not options.fromScript
|
||||
|
||||
# Need to escape every path to avoid errors
|
||||
resinWritePath = "\"#{path.join(__dirname, '..', '..', 'bin', 'resin-write')}\""
|
||||
elevate.run("\"#{process.argv[0]}\" #{resinWritePath} \"#{params.image}\" \"#{params.device}\"")
|
||||
else
|
||||
return done(error)
|
||||
console.info('Initializing device')
|
||||
resin.models.device.getManifestBySlug(options.type)
|
||||
.then (manifest) ->
|
||||
return manifest.initialization?.options
|
||||
.then (questions) ->
|
||||
return form.run questions,
|
||||
override:
|
||||
drive: options.drive
|
||||
.tap (answers) ->
|
||||
return if not answers.drive?
|
||||
message = "This will erase #{answers.drive}. Are you sure?"
|
||||
patterns.confirm(options.yes, message)
|
||||
.return(answers.drive)
|
||||
.then(umount.umountAsync)
|
||||
.tap (answers) ->
|
||||
return init.initialize(params.image, options.type, answers).then(stepHandler)
|
||||
.then (answers) ->
|
||||
return if not answers.drive?
|
||||
umount.umountAsync(answers.drive).tap ->
|
||||
console.info("You can safely remove #{answers.drive} now")
|
||||
.nodeify(done)
|
||||
|
@ -1,94 +0,0 @@
|
||||
_ = require('lodash')
|
||||
visuals = require('resin-cli-visuals')
|
||||
commandOptions = require('./command-options')
|
||||
plugins = require('../plugins')
|
||||
|
||||
exports.list =
|
||||
signature: 'plugins'
|
||||
description: 'list all plugins'
|
||||
help: '''
|
||||
Use this command to list all the installed resin plugins.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugins
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
plugins.list (error, resinPlugins) ->
|
||||
return done(error) if error?
|
||||
|
||||
if _.isEmpty(resinPlugins)
|
||||
console.log('You don\'t have any plugins yet')
|
||||
return done()
|
||||
|
||||
console.log visuals.widgets.table.horizontal resinPlugins, [
|
||||
'name'
|
||||
'version'
|
||||
'description'
|
||||
'license'
|
||||
]
|
||||
|
||||
return done()
|
||||
|
||||
exports.install =
|
||||
signature: 'plugin install <name>'
|
||||
description: 'install a plugin'
|
||||
help: '''
|
||||
Use this command to install a resin plugin
|
||||
|
||||
Use `--quiet` to prevent information logging.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin install hello
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
plugins.install params.name, (error) ->
|
||||
return done(error) if error?
|
||||
console.info("Plugin installed: #{params.name}")
|
||||
return done()
|
||||
|
||||
exports.update =
|
||||
signature: 'plugin update <name>'
|
||||
description: 'update a plugin'
|
||||
help: '''
|
||||
Use this command to update a resin plugin
|
||||
|
||||
Use `--quiet` to prevent information logging.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin update hello
|
||||
'''
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
plugins.update params.name, (error, version) ->
|
||||
return done(error) if error?
|
||||
console.info("Plugin updated: #{params.name}@#{version}")
|
||||
return done()
|
||||
|
||||
exports.remove =
|
||||
signature: 'plugin rm <name>'
|
||||
description: 'remove a plugin'
|
||||
help: '''
|
||||
Use this command to remove a resin.io plugin.
|
||||
|
||||
Notice this command asks for confirmation interactively.
|
||||
You can avoid this by passing the `--yes` boolean option.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin plugin rm hello
|
||||
$ resin plugin rm hello --yes
|
||||
'''
|
||||
options: [ commandOptions.yes ]
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
visuals.patterns.remove 'plugin', options.yes, (callback) ->
|
||||
plugins.remove(params.name, callback)
|
||||
, (error) ->
|
||||
return done(error) if error?
|
||||
console.info("Plugin removed: #{params.name}")
|
||||
return done()
|
@ -1,21 +0,0 @@
|
||||
open = require('open')
|
||||
url = require('url')
|
||||
settings = require('resin-settings-client')
|
||||
|
||||
exports.preferences =
|
||||
signature: 'preferences'
|
||||
description: 'open preferences form'
|
||||
help: '''
|
||||
Use this command to open the preferences form.
|
||||
|
||||
In the future, we will allow changing all preferences directly from the terminal.
|
||||
For now, we open your default web browser and point it to the web based preferences form.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin preferences
|
||||
'''
|
||||
permission: 'user'
|
||||
action: ->
|
||||
absUrl = url.resolve(settings.get('remoteUrl'), '/preferences')
|
||||
open(absUrl)
|
@ -1,58 +0,0 @@
|
||||
_ = require('lodash')
|
||||
child_process = require('child_process')
|
||||
president = require('president')
|
||||
npm = require('../npm')
|
||||
packageJSON = require('../../package.json')
|
||||
|
||||
exports.update =
|
||||
signature: 'update'
|
||||
description: 'update the resin cli'
|
||||
help: '''
|
||||
Use this command to update the Resin CLI
|
||||
|
||||
This command outputs information about the update process.
|
||||
Use `--quiet` to remove that output.
|
||||
|
||||
The Resin CLI checks for updates once per day.
|
||||
|
||||
Major updates require a manual update with this update command,
|
||||
while minor updates are applied automatically.
|
||||
|
||||
Examples:
|
||||
|
||||
$ resin update
|
||||
'''
|
||||
action: (params, options, done) ->
|
||||
npm.isUpdated packageJSON.name, packageJSON.version, (error, isUpdated) ->
|
||||
return done(error) if error?
|
||||
|
||||
if isUpdated
|
||||
return done(new Error('You\'re already running the latest version.'))
|
||||
|
||||
onUpdate = (error, stdout, stderr) ->
|
||||
return done(error) if error?
|
||||
return done(new Error(stderr)) if not _.isEmpty(stderr)
|
||||
console.info("Upgraded #{packageJSON.name}.")
|
||||
return done()
|
||||
|
||||
command = "npm install --global #{packageJSON.name}"
|
||||
|
||||
# Attempting to self update using the NPM API was not considered safe.
|
||||
# A safer thing to do is to call npm as a child process
|
||||
# https://github.com/npm/npm/issues/7723
|
||||
child_process.exec command, (error, stdout, stderr) ->
|
||||
return onUpdate(null, stdout, stderr) if not error?
|
||||
|
||||
if _.any [
|
||||
|
||||
# 3 is equivalent to EACCES for NPM
|
||||
error.code is 3
|
||||
|
||||
error.code is 'EPERM'
|
||||
error.code is 'ACCES'
|
||||
]
|
||||
return president.execute(command, onUpdate)
|
||||
|
||||
return done(error)
|
||||
|
||||
|
42
lib/actions/wizard.coffee
Normal file
42
lib/actions/wizard.coffee
Normal file
@ -0,0 +1,42 @@
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
resin = require('resin-sdk')
|
||||
patterns = require('../utils/patterns')
|
||||
|
||||
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
|
||||
'''
|
||||
permission: 'user'
|
||||
primary: true
|
||||
action: (params, options, done) ->
|
||||
Promise.try ->
|
||||
return if params.name?
|
||||
patterns.selectOrCreateApplication().tap (applicationName) ->
|
||||
resin.models.application.has(applicationName).then (hasApplication) ->
|
||||
return applicationName if hasApplication
|
||||
capitano.runAsync("app create #{applicationName}")
|
||||
.then (applicationName) ->
|
||||
params.name = applicationName
|
||||
.then ->
|
||||
return capitano.runAsync("device init --application #{params.name}")
|
||||
.tap(patterns.awaitDevice)
|
||||
.then (uuid) ->
|
||||
return capitano.runAsync("device #{uuid}")
|
||||
.then ->
|
||||
console.log('Your device is ready, start pushing some code!')
|
||||
.nodeify(done)
|
113
lib/app.coffee
113
lib/app.coffee
@ -1,56 +1,32 @@
|
||||
_ = require('lodash')
|
||||
async = require('async')
|
||||
capitano = require('capitano')
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
resin = require('resin-sdk')
|
||||
actions = require('./actions')
|
||||
errors = require('./errors')
|
||||
plugins = require('./plugins')
|
||||
update = require('./update')
|
||||
plugins = require('./utils/plugins')
|
||||
update = require('./utils/update')
|
||||
|
||||
capitano.permission 'user', (done) ->
|
||||
resin.auth.isLoggedIn (isLoggedIn) ->
|
||||
resin.auth.isLoggedIn().then (isLoggedIn) ->
|
||||
if not isLoggedIn
|
||||
return done(new Error('You have to log in'))
|
||||
return done()
|
||||
throw new Error ('You have to log in')
|
||||
.nodeify(done)
|
||||
|
||||
capitano.command
|
||||
signature: '*'
|
||||
action: ->
|
||||
capitano.execute(command: 'help')
|
||||
|
||||
# ---------- Options ----------
|
||||
capitano.globalOption
|
||||
signature: 'quiet'
|
||||
description: 'quiet (no output)'
|
||||
boolean: true
|
||||
alias: 'q'
|
||||
|
||||
capitano.globalOption
|
||||
signature: 'project'
|
||||
parameter: 'path'
|
||||
description: 'project path'
|
||||
alias: 'j'
|
||||
|
||||
capitano.globalOption
|
||||
signature: 'version'
|
||||
description: actions.info.version.description
|
||||
boolean: true
|
||||
alias: 'v'
|
||||
|
||||
# We don't do anything in response to this options
|
||||
# explicitly. We use InquirerJS to provide CLI widgets,
|
||||
# and that module understands --no-color automatically.
|
||||
capitano.globalOption
|
||||
signature: 'no-color'
|
||||
description: 'disable colour highlighting'
|
||||
boolean: true
|
||||
|
||||
# ---------- Info Module ----------
|
||||
capitano.command(actions.info.version)
|
||||
|
||||
# ---------- 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)
|
||||
@ -60,31 +36,22 @@ capitano.command(actions.auth.whoami)
|
||||
# ---------- App Module ----------
|
||||
capitano.command(actions.app.create)
|
||||
capitano.command(actions.app.list)
|
||||
capitano.command(actions.app.info)
|
||||
capitano.command(actions.app.remove)
|
||||
capitano.command(actions.app.restart)
|
||||
capitano.command(actions.app.associate)
|
||||
capitano.command(actions.app.init)
|
||||
capitano.command(actions.app.info)
|
||||
|
||||
# ---------- Device Module ----------
|
||||
capitano.command(actions.device.list)
|
||||
capitano.command(actions.device.supported)
|
||||
capitano.command(actions.device.rename)
|
||||
capitano.command(actions.device.init)
|
||||
capitano.command(actions.device.info)
|
||||
capitano.command(actions.device.remove)
|
||||
capitano.command(actions.device.identify)
|
||||
capitano.command(actions.device.await)
|
||||
|
||||
# ---------- Drive Module ----------
|
||||
capitano.command(actions.drive.list)
|
||||
capitano.command(actions.device.register)
|
||||
capitano.command(actions.device.info)
|
||||
|
||||
# ---------- Notes Module ----------
|
||||
capitano.command(actions.notes.set)
|
||||
|
||||
# ---------- Preferences Module ----------
|
||||
capitano.command(actions.preferences.preferences)
|
||||
|
||||
# ---------- Keys Module ----------
|
||||
capitano.command(actions.keys.list)
|
||||
capitano.command(actions.keys.add)
|
||||
@ -97,53 +64,17 @@ capitano.command(actions.env.add)
|
||||
capitano.command(actions.env.rename)
|
||||
capitano.command(actions.env.remove)
|
||||
|
||||
# ---------- Logs Module ----------
|
||||
capitano.command(actions.logs.logs)
|
||||
|
||||
# ---------- OS Module ----------
|
||||
capitano.command(actions.os.download)
|
||||
capitano.command(actions.os.install)
|
||||
capitano.command(actions.os.configure)
|
||||
capitano.command(actions.os.initialize)
|
||||
|
||||
# ---------- Examples Module ----------
|
||||
capitano.command(actions.examples.list)
|
||||
capitano.command(actions.examples.clone)
|
||||
capitano.command(actions.examples.info)
|
||||
# ---------- Logs Module ----------
|
||||
capitano.command(actions.logs)
|
||||
|
||||
# ---------- Plugins Module ----------
|
||||
capitano.command(actions.plugin.list)
|
||||
capitano.command(actions.plugin.install)
|
||||
capitano.command(actions.plugin.update)
|
||||
capitano.command(actions.plugin.remove)
|
||||
update.notify()
|
||||
|
||||
# ---------- Update Module ----------
|
||||
capitano.command(actions.update.update)
|
||||
|
||||
changeProjectDirectory = (directory) ->
|
||||
try
|
||||
process.chdir(directory)
|
||||
catch
|
||||
errors.handle(new Error("Invalid project: #{directory}"))
|
||||
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
update.check(callback)
|
||||
|
||||
(callback) ->
|
||||
plugins.register('resin-plugin-', callback)
|
||||
|
||||
(callback) ->
|
||||
cli = capitano.parse(process.argv)
|
||||
|
||||
if cli.global.quiet
|
||||
console.info = _.noop
|
||||
|
||||
if cli.global.project?
|
||||
changeProjectDirectory(cli.global.project)
|
||||
|
||||
if cli.global.version
|
||||
actions.info.version.action(null, null, callback)
|
||||
else
|
||||
capitano.execute(cli, callback)
|
||||
|
||||
], errors.handle)
|
||||
plugins.register(/^resin-plugin-(.+)$/).then ->
|
||||
cli = capitano.parse(process.argv)
|
||||
capitano.executeAsync(cli)
|
||||
.catch(errors.handle)
|
||||
|
@ -1,96 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "basic-resin-node-project",
|
||||
"display_name": "Node.js Starter Project",
|
||||
"repository": "https://github.com/resin-io/basic-resin-node-project",
|
||||
"description": "This is a simple Hello, World project for node.js designed to act as a basis for future work. It demonstrates how to install native Linux packages and configure your application."
|
||||
},
|
||||
{
|
||||
"name": "cimon",
|
||||
"display_name": "Cimon",
|
||||
"repository": "https://bitbucket.org/efwe/cimon",
|
||||
"description": "A simple tool for reading temperatures from a USB-enabled thermometer. This project is used as the backend to efwe's awesome temperature visualisation at 123k.de.",
|
||||
"author": "efwe"
|
||||
},
|
||||
{
|
||||
"name": "firebaseDTL",
|
||||
"display_name": "Digital Temperature Logger",
|
||||
"repository": "https://github.com/shaunmulligan/firebaseDTL",
|
||||
"description": "A Firebase-backed Digital Temperature Logger allowing you to connect devices with multiple temperature sensors to a central cloud-based datastore.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "digitiser",
|
||||
"display_name": "Digitiser",
|
||||
"repository": "https://github.com/shaunmulligan/digitiser",
|
||||
"description": "A tool for displaying integer values from a JSON endpoint on a MAX7219 7-segment display.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "basic-gpio",
|
||||
"display_name": "Example Pi Pins Application",
|
||||
"repository": "https://github.com/shaunmulligan/basic-gpio",
|
||||
"description": "A simple application which demonstrates the use of the Pi Pins library to interface with GPIO.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "coder",
|
||||
"display_name": "Google Coder",
|
||||
"repository": "https://github.com/resin-io/coder",
|
||||
"description": "Resin.io-enabled version of Google's excellent Coder project which makes it easy to develop web projects on your device."
|
||||
},
|
||||
{
|
||||
"name": "hoversnap",
|
||||
"display_name": "Hoversnap",
|
||||
"repository": "https://github.com/resin-io/hoversnap",
|
||||
"description": "A tool for controlling a camera using a foot switch in order to capture shots in which people appear to be flying."
|
||||
},
|
||||
{
|
||||
"name": "resin-piminer",
|
||||
"display_name": "Pi Miner",
|
||||
"repository": "https://github.com/csquared/resin-piminer",
|
||||
"description": "A bitcoin miner for the Raspberry Pi.",
|
||||
"author": "Chris Continanza"
|
||||
},
|
||||
{
|
||||
"name": "resin-cctv",
|
||||
"display_name": "Resin CCTV",
|
||||
"repository": "https://github.com/abresas/resin-cctv",
|
||||
"description": "A project which allows you to use your devices as a CCTV camera system which hooks into Dropbox.",
|
||||
"author": "Aleksis Brezas"
|
||||
},
|
||||
{
|
||||
"name": "resin_player",
|
||||
"display_name": "Resin Player",
|
||||
"repository": "https://bitbucket.org/lifeeth/resin_player/",
|
||||
"description": "A project which allows you to play squeezebox media through your devices.",
|
||||
"author": "Praneeth Bodduluri"
|
||||
},
|
||||
{
|
||||
"name": "salesforceTemp",
|
||||
"display_name": "Salesforce Temperature Probe",
|
||||
"repository": "https://github.com/shaunmulligan/salesforceTemp",
|
||||
"description": "Example application for interfacing with a temperature probe using Salesforce.com.",
|
||||
"author": "Shaun Mulligan"
|
||||
},
|
||||
{
|
||||
"name": "sms2speech",
|
||||
"display_name": "SMS to Speech",
|
||||
"repository": "https://github.com/alexandrosm/sms2speech",
|
||||
"description": "A simple tool which uses Twillio to read out incoming SMS messages.",
|
||||
"author": "Alexandros Marinos"
|
||||
},
|
||||
{
|
||||
"name": "resin-kiosk",
|
||||
"display_name": "Simple Digitiser Kiosk",
|
||||
"repository": "https://bitbucket.org/lifeeth/resin-kiosk",
|
||||
"description": "Displays values from a JSON endpoint on your browser in kiosk mode",
|
||||
"author": "Praneeth Bodduluri"
|
||||
},
|
||||
{
|
||||
"name": "text2speech",
|
||||
"display_name": "Text to Speech Converter",
|
||||
"repository": "https://github.com/resin-io/text2speech",
|
||||
"description": "A simple application that makes your device speak out loud."
|
||||
}
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
_ = require('lodash')
|
||||
os = require('os')
|
||||
path = require('path')
|
||||
|
||||
isWindows = ->
|
||||
return os.platform() is 'win32'
|
||||
|
||||
exports.shouldElevate = (error) ->
|
||||
return _.all [
|
||||
isWindows()
|
||||
error.code is 'EPERM' or error.code is 'EACCES'
|
||||
]
|
||||
|
||||
exports.run = (command) ->
|
||||
return if not isWindows()
|
||||
require('windosu').exec(command)
|
@ -1,40 +1,13 @@
|
||||
_ = require('lodash')
|
||||
os = require('os')
|
||||
chalk = require('chalk')
|
||||
errors = require('resin-cli-errors')
|
||||
patterns = require('./utils/patterns')
|
||||
|
||||
exports.handle = (error, exit = true) ->
|
||||
return if not error? or error not instanceof Error
|
||||
exports.handle = (error) ->
|
||||
message = errors.interpret(error)
|
||||
return if not message?
|
||||
|
||||
if process.env.DEBUG
|
||||
console.error(error.stack)
|
||||
else
|
||||
if error.code is 'EISDIR'
|
||||
console.error("File is a directory: #{error.path}")
|
||||
message = error.stack
|
||||
|
||||
else if error.code is 'ENOENT'
|
||||
console.error("No such file or directory: #{error.path}")
|
||||
|
||||
else if error.code is 'EACCES' or error.code is 'EPERM'
|
||||
message = 'You don\'t have enough privileges to run this operation.\n'
|
||||
|
||||
if os.platform() is 'win32'
|
||||
message += 'Run a new Command Prompt as administrator and try running this command again.'
|
||||
else
|
||||
message += 'Try running this command again prefixing it with `sudo`.'
|
||||
|
||||
console.error(message)
|
||||
|
||||
else if error.code is 'ENOGIT'
|
||||
console.error '''
|
||||
Git is not installed on this system.
|
||||
Head over to http://git-scm.com to install it and run this command again.
|
||||
'''
|
||||
|
||||
else if error.message?
|
||||
console.error(error.message)
|
||||
|
||||
if _.isNumber(error.exitCode)
|
||||
errorCode = error.exitCode
|
||||
else
|
||||
errorCode = 1
|
||||
|
||||
process.exit(errorCode) if exit
|
||||
patterns.printErrorMessage(message)
|
||||
process.exit(error.exitCode or 1)
|
||||
|
@ -1,29 +0,0 @@
|
||||
npm = require('npm')
|
||||
async = require('async')
|
||||
_ = require('lodash-contrib')
|
||||
|
||||
exports.getLatestVersion = (name, callback) ->
|
||||
async.waterfall([
|
||||
|
||||
(callback) ->
|
||||
options =
|
||||
|
||||
# TODO: There is no way to quiet npm install completely.
|
||||
# Some output is still shown once the module is updated
|
||||
# https://github.com/npm/npm/issues/2040
|
||||
loglevel: 'silent'
|
||||
global: true
|
||||
|
||||
npm.load(options, _.unary(callback))
|
||||
|
||||
(callback) ->
|
||||
npm.commands.view [ name ], true, (error, data) ->
|
||||
versions = _.keys(data)
|
||||
return callback(error, _.first(versions))
|
||||
|
||||
], callback)
|
||||
|
||||
exports.isUpdated = (name, currentVersion, callback) ->
|
||||
exports.getLatestVersion name, (error, latestVersion) ->
|
||||
return callback(error) if error?
|
||||
return callback(null, currentVersion is latestVersion)
|
@ -1,34 +0,0 @@
|
||||
Nplugm = require('nplugm')
|
||||
_ = require('lodash')
|
||||
capitano = require('capitano')
|
||||
|
||||
nplugm = null
|
||||
|
||||
registerPlugin = (plugin) ->
|
||||
return capitano.command(plugin) if not _.isArray(plugin)
|
||||
return _.each(plugin, capitano.command)
|
||||
|
||||
exports.register = (prefix, callback) ->
|
||||
nplugm = new Nplugm(prefix)
|
||||
nplugm.list (error, plugins) ->
|
||||
return callback(error) if error?
|
||||
|
||||
for plugin in plugins
|
||||
try
|
||||
registerPlugin(nplugm.require(plugin))
|
||||
catch error
|
||||
console.error(error.message)
|
||||
|
||||
return callback()
|
||||
|
||||
exports.list = ->
|
||||
nplugm.list.apply(nplugm, arguments)
|
||||
|
||||
exports.install = ->
|
||||
nplugm.install.apply(nplugm, arguments)
|
||||
|
||||
exports.update = ->
|
||||
nplugm.update.apply(nplugm, arguments)
|
||||
|
||||
exports.remove = ->
|
||||
nplugm.remove.apply(nplugm, arguments)
|
@ -1,26 +0,0 @@
|
||||
updateNotifier = require('update-notifier')
|
||||
packageJSON = require('../package.json')
|
||||
updateAction = require('./actions/update')
|
||||
|
||||
exports.perform = (callback) ->
|
||||
updateAction.update.action(null, null, callback)
|
||||
|
||||
exports.notify = (update) ->
|
||||
return if not process.stdout.isTTY
|
||||
|
||||
console.log """
|
||||
> Major update available: #{update.current} -> #{update.latest}
|
||||
> Run resin update to update.
|
||||
> Beware that a major release might introduce breaking changes.\n
|
||||
"""
|
||||
|
||||
exports.check = (callback) ->
|
||||
notifier = updateNotifier(pkg: packageJSON)
|
||||
return callback() if not notifier.update?
|
||||
|
||||
if notifier.update.type is 'major'
|
||||
exports.notify(notifier.update)
|
||||
return callback()
|
||||
|
||||
console.log("Performing #{notifier.update.type} update, hold tight...")
|
||||
exports.perform(callback)
|
44
lib/utils/helpers.coffee
Normal file
44
lib/utils/helpers.coffee
Normal file
@ -0,0 +1,44 @@
|
||||
Promise = require('bluebird')
|
||||
capitano = Promise.promisifyAll(require('capitano'))
|
||||
_ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
child_process = require('child_process')
|
||||
rindle = require('rindle')
|
||||
os = require('os')
|
||||
chalk = require('chalk')
|
||||
|
||||
exports.getGroupDefaults = (group) ->
|
||||
return _.chain(group)
|
||||
.get('options')
|
||||
.map (question) ->
|
||||
return [ question.name, question.default ]
|
||||
.object()
|
||||
.value()
|
||||
|
||||
exports.stateToString = (state) ->
|
||||
percentage = _.str.lpad(state.percentage, 3, '0') + '%'
|
||||
result = "#{chalk.blue(percentage)} #{chalk.cyan(state.operation.command)}"
|
||||
|
||||
switch state.operation.command
|
||||
when 'copy'
|
||||
return "#{result} #{state.operation.from.path} -> #{state.operation.to.path}"
|
||||
when 'replace'
|
||||
return "#{result} #{state.operation.file.path}, #{state.operation.copy} -> #{state.operation.replace}"
|
||||
when 'run-script'
|
||||
return "#{result} #{state.operation.script}"
|
||||
else
|
||||
throw new Error("Unsupported operation: #{state.operation.type}")
|
||||
|
||||
exports.sudo = (command) ->
|
||||
|
||||
# Bypass privilege elevation for Windows for now.
|
||||
# We should use `windosu` in this case.
|
||||
if os.platform() is 'win32'
|
||||
return capitano.runAsync(command.join(' '))
|
||||
|
||||
command = _.union(_.take(process.argv, 2), command)
|
||||
|
||||
spawn = child_process.spawn 'sudo', command,
|
||||
stdio: 'inherit'
|
||||
|
||||
return rindle.wait(spawn)
|
80
lib/utils/patterns.coffee
Normal file
80
lib/utils/patterns.coffee
Normal file
@ -0,0 +1,80 @@
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
form = require('resin-cli-form')
|
||||
visuals = require('resin-cli-visuals')
|
||||
resin = require('resin-sdk')
|
||||
chalk = require('chalk')
|
||||
validation = require('./validation')
|
||||
|
||||
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) ->
|
||||
if not hasAnyApplications
|
||||
throw new Error('You don\'t have any applications')
|
||||
|
||||
resin.models.application.getAll().then (applications) ->
|
||||
return form.ask
|
||||
message: 'Select an application'
|
||||
type: 'list'
|
||||
choices: _.pluck(applications, 'app_name')
|
||||
|
||||
exports.selectOrCreateApplication = ->
|
||||
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'
|
||||
validate: validation.validateApplicationName
|
||||
|
||||
exports.awaitDevice = (uuid) ->
|
||||
resin.models.device.getName(uuid).then (deviceName) ->
|
||||
spinner = new visuals.Spinner("Waiting for #{deviceName} to come online")
|
||||
|
||||
poll = ->
|
||||
resin.models.device.isOnline(uuid).then (isOnline) ->
|
||||
if isOnline
|
||||
spinner.stop()
|
||||
console.info("Device became online: #{deviceName}")
|
||||
return
|
||||
else
|
||||
|
||||
# Spinner implementation is smart enough to
|
||||
# not start again if it was already started
|
||||
spinner.start()
|
||||
|
||||
return Promise.delay(3000).then(poll)
|
||||
|
||||
console.info("Waiting for #{deviceName} to connect to resin...")
|
||||
poll().return(uuid)
|
||||
|
||||
exports.printErrorMessage = (message) ->
|
||||
console.error(chalk.red(message))
|
13
lib/utils/plugins.coffee
Normal file
13
lib/utils/plugins.coffee
Normal file
@ -0,0 +1,13 @@
|
||||
nplugm = require('nplugm')
|
||||
_ = require('lodash')
|
||||
capitano = require('capitano')
|
||||
patterns = require('./patterns')
|
||||
|
||||
exports.register = (regex) ->
|
||||
nplugm.list(regex).map (plugin) ->
|
||||
command = require(plugin)
|
||||
command.plugin = true
|
||||
return capitano.command(command) if not _.isArray(command)
|
||||
return _.each(command, capitano.command)
|
||||
.catch (error) ->
|
||||
patterns.printErrorMessage(error.message)
|
18
lib/utils/update.coffee
Normal file
18
lib/utils/update.coffee
Normal file
@ -0,0 +1,18 @@
|
||||
updateNotifier = require('update-notifier')
|
||||
isRoot = require('is-root')
|
||||
packageJSON = require('../../package.json')
|
||||
|
||||
# `update-notifier` creates files to make the next
|
||||
# running time ask for updated, however this can lead
|
||||
# to ugly EPERM issues if those files are created as root.
|
||||
if not isRoot()
|
||||
notifier = updateNotifier(pkg: packageJSON)
|
||||
|
||||
exports.hasAvailableUpdate = ->
|
||||
return notifier?
|
||||
|
||||
exports.notify = ->
|
||||
return if not exports.hasAvailableUpdate()
|
||||
notifier.notify(defer: false)
|
||||
if notifier.update?
|
||||
console.log('Notice that you might need administrator privileges depending on your setup\n')
|
19
lib/utils/validation.coffee
Normal file
19
lib/utils/validation.coffee
Normal file
@ -0,0 +1,19 @@
|
||||
validEmail = require('valid-email')
|
||||
|
||||
exports.validateEmail = (input) ->
|
||||
if not validEmail(input)
|
||||
return 'Email is not valid'
|
||||
|
||||
return true
|
||||
|
||||
exports.validatePassword = (input) ->
|
||||
if input.length < 8
|
||||
return 'Password should be 8 characters long'
|
||||
|
||||
return true
|
||||
|
||||
exports.validateApplicationName = (input) ->
|
||||
if input.length < 4
|
||||
return 'The application name should be at least 4 characters'
|
||||
|
||||
return true
|
@ -1,48 +0,0 @@
|
||||
.TH "RESIN" "1" "March 2015" "" ""
|
||||
.SH "NAME"
|
||||
\fBresin\fR \- tab completion for resin
|
||||
.SH DESCRIPTION
|
||||
.P
|
||||
It provides basic completion capabilities for \fBzsh\fR and \fBbash\fR\|\.
|
||||
.P
|
||||
If you're using \fBbash\fR, add the following line to your \fB~/\.bashrc\fR:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
\|\. /path/to/resin/completion/resin\.sh\.
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
or create a symlink like this if you have automatic bash completion set up:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
ln \- /path/to/resin/completion/resin\.sh /etc/bash\-completion\.d/resin
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
or, perhaps:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
ln \- /path/to/resin/completion/resin\.sh /usr/local/etc/bash\-completion\.d/resin
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
If you're using \fBzsh\fR, add the following to your \fB~/\.zshrc\fR:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
autoload bashcompinit
|
||||
bashcompinit
|
||||
source /path/to/resin/completion/resin\.sh
|
||||
.fi
|
||||
.RE
|
||||
.SH RESIN PATH
|
||||
.P
|
||||
\fB/path/to/resin\fR refers to the place where resin was globally installed in your system\.
|
||||
.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\.
|
||||
.SH COPYRIGHT
|
||||
.P
|
||||
resin is Copyright (C) 2014 Resin\.io https://resin\.io
|
@ -1,34 +0,0 @@
|
||||
resin(1) - tab completion for resin
|
||||
===================================
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
It provides basic completion capabilities for `zsh` and `bash`.
|
||||
|
||||
If you're using `bash`, add the following line to your `~/.bashrc`:
|
||||
|
||||
. /path/to/resin/completion/resin.sh.
|
||||
|
||||
or create a symlink like this if you have automatic bash completion set up:
|
||||
|
||||
ln - /path/to/resin/completion/resin.sh /etc/bash-completion.d/resin
|
||||
|
||||
or, perhaps:
|
||||
|
||||
ln - /path/to/resin/completion/resin.sh /usr/local/etc/bash-completion.d/resin
|
||||
|
||||
If you're using `zsh`, add the following to your `~/.zshrc`:
|
||||
|
||||
autoload bashcompinit
|
||||
bashcompinit
|
||||
source /path/to/resin/completion/resin.sh
|
||||
|
||||
## RESIN PATH
|
||||
|
||||
`/path/to/resin` refers to the place where resin was globally installed in your system.
|
||||
|
||||
This is usually something like `/usr/lib/node_modules/resin`, or `C:\Users\AppData\Roaming\npm\node_modules\resin` on Windows.
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
resin is Copyright (C) 2014 Resin.io <https://resin.io>
|
@ -1,196 +0,0 @@
|
||||
.TH "RESIN\-PLUGINS" "1" "March 2015" "" ""
|
||||
.SH "NAME"
|
||||
\fBresin-plugins\fR \- Creating Resin CLI plugins
|
||||
.SH DESCRIPTION
|
||||
.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\.
|
||||
.SH TUTORIAL
|
||||
.P
|
||||
In this guide, we'll create a simple hello plugin that greets the user\.
|
||||
.P
|
||||
Create a directory called \fBresin\-plugin\-hello\fR, containing a single \fBindex\.js\fR file\.
|
||||
.P
|
||||
Within the new project, run \fBnpm init\fR and make sure the package name is set to \fBresin\-plugin\-hello\fR as well\.
|
||||
.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\.
|
||||
.P
|
||||
Your \fBpackage\.json\fR should look something like this:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
{
|
||||
"name": "resin\-plugin\-hello",
|
||||
"version": "1\.0\.0",
|
||||
"main": "index\.js",
|
||||
"description": "My first Resin plugin",
|
||||
"license": "MIT"
|
||||
}
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
Your index file should export an object (if exposing a single command) or an array of objects (if exposing multiple commands)\.
|
||||
.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\.
|
||||
.P
|
||||
Each object describes a single command\. The accepted fields are:
|
||||
.RS 0
|
||||
.IP \(bu 2
|
||||
\fBsignature\fR: A Capitano \fIhttps://github\.com/resin\-io/capitano\fR signature\.
|
||||
.IP \(bu 2
|
||||
\fBdescription\fR: A string containing a short description of the command\. This will be shown on the Resin general help\.
|
||||
.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\.
|
||||
.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)\.
|
||||
.IP \(bu 2
|
||||
\fBpermission\fR: A string describing the required permissions to run the command\.
|
||||
.IP \(bu 2
|
||||
\fBoptions\fR: An array of Capitano \fIhttps://github\.com/resin\-io/capitano\fR options\.
|
||||
|
||||
.RE
|
||||
.P
|
||||
The \fBindex\.js\fR file should look something like:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
module\.exports = [
|
||||
{
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin\.',
|
||||
action: function(params, options, done) {
|
||||
console\.log('Hey there ' + params\.name + '!');
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
As we're only exporting a single command, we can export the object directly:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
module\.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
action: function(params, options, done) {
|
||||
console\.log('Hey there ' + params\.name + '!');
|
||||
done();
|
||||
}
|
||||
}
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
This example will register a \fBhello\fR command which requires a \fBname\fR parameter, and greets the user in result\.
|
||||
.P
|
||||
To test the plugin, first create a global link by running the following command inside your plugin directory:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
$ npm link
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
Now if you run \fB$ resin help\fR you should see your new command at the bottom of the list\.
|
||||
.P
|
||||
Try it out:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
$ resin hello Juan
|
||||
Hey there Juan!
|
||||
.fi
|
||||
.RE
|
||||
.SH DONE CALLBACK
|
||||
.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\.
|
||||
.P
|
||||
If your action is synchronous and doesn't return any error, you can omit the \fBdone()\fR callback all together\. For example:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
module\.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
action: function(params, options) {
|
||||
console\.log('Hey there ' + params\.name + '!');
|
||||
}
|
||||
}
|
||||
.fi
|
||||
.RE
|
||||
.SH PERMISSIONS
|
||||
.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\.
|
||||
.P
|
||||
To require the user to login before calling our hello plugin, we can add \fBpermission: 'user'\fR to the command description:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
module\.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
console\.log('Hey there ' + params\.name + '!');
|
||||
done();
|
||||
}
|
||||
}
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
Now if the user attempts to call our command without being logged in, a nice error message asking him to login will be shown instead\.
|
||||
.SH OPTIONS
|
||||
.P
|
||||
You can define certain options that your command accepts\. Notice these are per command, and thus are not available to other command that doesn't declares them as well\.
|
||||
.P
|
||||
Let's say we want to allow the user to configure the greeting language\. For example:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
$ resin hello Juan \-\-language spanish
|
||||
.fi
|
||||
.RE
|
||||
.P
|
||||
We first need to register the \fBlanguage option\fR:
|
||||
.P
|
||||
.RS 2
|
||||
.nf
|
||||
module\.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
options: [
|
||||
{
|
||||
signature: 'language',
|
||||
parameter: 'language',
|
||||
description: 'the greeting language',
|
||||
alias: 'l'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
if(options\.language === 'spanish') {
|
||||
console\.log('Hola ' + params\.name + '!');
|
||||
} else {
|
||||
console\.log('Hey there ' + params\.name + '!');
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
.fi
|
||||
.RE
|
||||
.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)\.
|
||||
.SH COFFEESCRIPT
|
||||
.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\.
|
||||
.SH RESIN\-SDK
|
||||
.P
|
||||
You can use the Resin SDK NodeJS module within your own plugins to communicate with Resin\.
|
||||
.SH RESIN\-CLI\-VISUALS
|
||||
.P
|
||||
Use the Resin CLI Visuals module to make use of the widgets used by the built\-in CLI commands\.
|
@ -1,159 +0,0 @@
|
||||
resin-plugins(1) - Creating Resin CLI plugins
|
||||
=============================================
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Resin CLI plugins are managed by NPM. Installing an NPM module that starts with `resin-plugin-*` globally will automatically make it available to the Resin CLI.
|
||||
|
||||
## TUTORIAL
|
||||
|
||||
In this guide, we'll create a simple hello plugin that greets the user.
|
||||
|
||||
Create a directory called `resin-plugin-hello`, containing a single `index.js` file.
|
||||
|
||||
Within the new project, run `npm init` and make sure the package name is set to `resin-plugin-hello` as well.
|
||||
|
||||
Also make sure that you have a `main` field in `package.json` that points to the `index.js` file you created above.
|
||||
|
||||
Your `package.json` should look something like this:
|
||||
|
||||
{
|
||||
"name": "resin-plugin-hello",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"description": "My first Resin plugin",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
Your index file should export an object (if exposing a single command) or an array of objects (if exposing multiple commands).
|
||||
|
||||
Notice that is very important that your `package.json` `main` field points to the file that is exporting the commands for the plugin to work correctly.
|
||||
|
||||
Each object describes a single command. The accepted fields are:
|
||||
|
||||
- `signature`: A [Capitano](https://github.com/resin-io/capitano) signature.
|
||||
- `description`: A string containing a short description of the command. This will be shown on the Resin general help.
|
||||
- `help`: A string containing an usage help page. This will be shown when passing the signature to the `help` command.
|
||||
- `action`: A function that defines the action to take when the command is matched. The function will be given 3 arguments (`params`, `options`, `done`).
|
||||
- `permission`: A string describing the required permissions to run the command.
|
||||
- `options`: An array of [Capitano](https://github.com/resin-io/capitano) options.
|
||||
|
||||
The `index.js` file should look something like:
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin.',
|
||||
action: function(params, options, done) {
|
||||
console.log('Hey there ' + params.name + '!');
|
||||
done();
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
As we're only exporting a single command, we can export the object directly:
|
||||
|
||||
module.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
action: function(params, options, done) {
|
||||
console.log('Hey there ' + params.name + '!');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
This example will register a `hello` command which requires a `name` parameter, and greets the user in result.
|
||||
|
||||
To test the plugin, first create a global link by running the following command inside your plugin directory:
|
||||
|
||||
$ npm link
|
||||
|
||||
Now if you run `$ resin help` you should see your new command at the bottom of the list.
|
||||
|
||||
Try it out:
|
||||
|
||||
$ resin hello Juan
|
||||
Hey there Juan!
|
||||
|
||||
## DONE CALLBACK
|
||||
|
||||
It's very important that you call the `done()` callback after your action finishes. If you pass an `Error` instance to `done()`, its message will be displayed by the Resin CLI, exiting with an error code 1.
|
||||
|
||||
If your action is synchronous and doesn't return any error, you can omit the `done()` callback all together. For example:
|
||||
|
||||
module.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
action: function(params, options) {
|
||||
console.log('Hey there ' + params.name + '!');
|
||||
}
|
||||
}
|
||||
|
||||
## PERMISSIONS
|
||||
|
||||
You can set a command permission to restrict access to the commands. Currently, the only registered permission is `user`, which requires the user to log in to Resin from the CLI.
|
||||
|
||||
To require the user to login before calling our hello plugin, we can add `permission: 'user'` to the command description:
|
||||
|
||||
module.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
permission: 'user',
|
||||
action: function(params, options, done) {
|
||||
console.log('Hey there ' + params.name + '!');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
Now if the user attempts to call our command without being logged in, a nice error message asking him to login will be shown instead.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
You can define certain options that your command accepts. Notice these are per command, and thus are not available to other command that doesn't declares them as well.
|
||||
|
||||
Let's say we want to allow the user to configure the greeting language. For example:
|
||||
|
||||
$ resin hello Juan --language spanish
|
||||
|
||||
We first need to register the `language option`:
|
||||
|
||||
module.exports = {
|
||||
signature: 'hello <name>',
|
||||
description: 'example plugin',
|
||||
help: 'This is an example plugin',
|
||||
options: [
|
||||
{
|
||||
signature: 'language',
|
||||
parameter: 'language',
|
||||
description: 'the greeting language',
|
||||
alias: 'l'
|
||||
}
|
||||
],
|
||||
action: function(params, options, done) {
|
||||
if(options.language === 'spanish') {
|
||||
console.log('Hola ' + params.name + '!');
|
||||
} else {
|
||||
console.log('Hey there ' + params.name + '!');
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
Here, we declared an option with a signature of `language` (so we can use it as `--language`), a parameter name of `language` as well (this means we'll be able to access the option as the `language` key: `options.language`), a nice description and an alias `l` (which means we can use `-l <language>` too).
|
||||
|
||||
## COFFEESCRIPT
|
||||
|
||||
We have CoffeeScript support out of the box. Implement your commands in `index.coffee` and point `package.json` `main` to that file.
|
||||
|
||||
## RESIN-SDK
|
||||
|
||||
You can use the Resin SDK NodeJS module within your own plugins to communicate with Resin.
|
||||
|
||||
## RESIN-CLI-VISUALS
|
||||
|
||||
Use the Resin CLI Visuals module to make use of the widgets used by the built-in CLI commands.
|
12
man/resin.1
12
man/resin.1
@ -1,12 +0,0 @@
|
||||
.TH "RESIN" "1" "March 2015" "" ""
|
||||
.SH "NAME"
|
||||
\fBresin\fR \- command line tool to interact with resin\.io
|
||||
.SH SYNOPSIS
|
||||
.P
|
||||
\fBresin\fR [options] <command>
|
||||
.SH DESCRIPTION
|
||||
.P
|
||||
\fBresin\fR is a powerful command line application to interact, develop and deploy resin\.io applications and devices\.
|
||||
.SH COPYRIGHT
|
||||
.P
|
||||
resin is Copyright (C) 2014 Resin\.io https://resin\.io
|
@ -1,14 +0,0 @@
|
||||
resin(1) - command line tool to interact with resin.io
|
||||
======================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`resin` [options] <command>
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**resin** is a powerful command line application to interact, develop and deploy resin.io applications and devices.
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
resin is Copyright (C) 2014 Resin.io <https://resin.io>
|
84
package.json
84
package.json
@ -1,19 +1,14 @@
|
||||
{
|
||||
"name": "resin-cli",
|
||||
"version": "0.10.4",
|
||||
"version": "2.0.1",
|
||||
"description": "Git Push to your devices",
|
||||
"main": "./lib/actions/index.coffee",
|
||||
"main": "./build/actions/index.js",
|
||||
"homepage": "https://github.com/resin-io/resin-cli",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:resin-io/resin-cli.git"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"man": [
|
||||
"./man/resin.1",
|
||||
"./man/resin-completion.1",
|
||||
"./man/resin-plugins.1"
|
||||
],
|
||||
"bin": {
|
||||
"resin": "./bin/resin"
|
||||
},
|
||||
@ -26,47 +21,50 @@
|
||||
"resin",
|
||||
"git"
|
||||
],
|
||||
"author": "Juan Cruz Viotti <juanchiviotti@gmail.com>",
|
||||
"author": "Juan Cruz Viotti <juan@resin.io>",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"windosu": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "~1.9.2",
|
||||
"chai": "^3.0.0",
|
||||
"ent": "^2.2.0",
|
||||
"gulp": "~3.8.9",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-coffee": "^2.2.0",
|
||||
"gulp-coffeelint": "~0.4.0",
|
||||
"gulp-marked-man": "~0.3.1",
|
||||
"gulp-mocha": "~1.1.1",
|
||||
"gulp-shell": "^0.2.11",
|
||||
"gulp-util": "~3.0.1",
|
||||
"mocha": "~2.0.1",
|
||||
"mocha-notifier-reporter": "~0.1.0",
|
||||
"run-sequence": "~1.0.2",
|
||||
"sinon": "~1.12.1",
|
||||
"sinon-chai": "~2.6.0"
|
||||
"gulp-coffeelint": "^0.5.0",
|
||||
"gulp-mocha": "^2.1.3",
|
||||
"gulp-shell": "^0.4.2",
|
||||
"gulp-util": "^3.0.6",
|
||||
"mocha": "^2.2.5",
|
||||
"run-sequence": "^1.1.1",
|
||||
"sinon": "^1.15.4",
|
||||
"sinon-chai": "^2.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.9.0",
|
||||
"capitano": "~1.5.0",
|
||||
"coffee-script": "~1.8.0",
|
||||
"conf.js": "^0.1.1",
|
||||
"drivelist": "^1.2.1",
|
||||
"lodash": "~2.4.1",
|
||||
"lodash-contrib": "~241.4.14",
|
||||
"mkdirp": "~0.5.0",
|
||||
"nplugm": "^2.2.0",
|
||||
"npm": "^2.6.1",
|
||||
"open": "0.0.5",
|
||||
"president": "^1.0.0",
|
||||
"resin-cli-visuals": "^0.1.0",
|
||||
"resin-sdk": "^1.4.1",
|
||||
"resin-image": "^1.1.1",
|
||||
"resin-settings-client": "^1.0.0",
|
||||
"resin-vcs": "^1.2.0",
|
||||
"tmp": "^0.0.25",
|
||||
"underscore.string": "~2.4.0",
|
||||
"update-notifier": "^0.3.1"
|
||||
"bluebird": "^2.9.34",
|
||||
"capitano": "~1.7.0",
|
||||
"chalk": "^1.1.1",
|
||||
"coffee-script": "^1.9.3",
|
||||
"columnify": "^1.5.2",
|
||||
"is-root": "^1.0.0",
|
||||
"lodash": "^3.10.0",
|
||||
"nplugm": "^3.0.0",
|
||||
"resin-cli-errors": "^1.0.0",
|
||||
"resin-cli-events": "^1.0.2",
|
||||
"resin-cli-form": "^1.3.0",
|
||||
"resin-cli-visuals": "^1.2.2",
|
||||
"resin-config-inject": "^2.0.0",
|
||||
"resin-device-init": "^2.0.0",
|
||||
"resin-image": "^1.1.4",
|
||||
"resin-image-manager": "^3.2.2",
|
||||
"resin-pine": "^1.3.0",
|
||||
"resin-sdk": "^4.0.0",
|
||||
"resin-settings-client": "^3.1.0",
|
||||
"resin-vcs": "^2.0.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"rindle": "^1.0.0",
|
||||
"tmp": "0.0.28",
|
||||
"umount": "^1.1.1",
|
||||
"underscore.string": "^3.1.1",
|
||||
"unzip2": "^0.2.5",
|
||||
"update-notifier": "^0.5.0",
|
||||
"valid-email": "0.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,88 +0,0 @@
|
||||
chai = require('chai')
|
||||
expect = chai.expect
|
||||
chai.use(require('sinon-chai'))
|
||||
_ = require('lodash')
|
||||
sinon = require('sinon')
|
||||
errors = require('../lib/errors')
|
||||
|
||||
describe 'Errors:', ->
|
||||
|
||||
describe '#handle()', ->
|
||||
|
||||
it 'should log the error message to stderr', ->
|
||||
message = 'Hello World'
|
||||
logErrorStub = sinon.stub(console, 'error')
|
||||
error = new Error(message)
|
||||
errors.handle(error, false)
|
||||
expect(logErrorStub).to.have.been.calledWith(message)
|
||||
logErrorStub.restore()
|
||||
|
||||
it 'should do nothing if error is not an instance of Error', ->
|
||||
logErrorStub = sinon.stub(console, 'error')
|
||||
|
||||
for item in [
|
||||
undefined
|
||||
null
|
||||
[ 1, 2, 3 ]
|
||||
'Hello'
|
||||
{ message: 'foo bar' }
|
||||
]
|
||||
errors.handle(item, false)
|
||||
|
||||
expect(logErrorStub).to.not.have.been.called
|
||||
logErrorStub.restore()
|
||||
|
||||
checkProcessExitOption = (error, value, expectations) ->
|
||||
processExitStub = sinon.stub(process, 'exit')
|
||||
logErrorStub = sinon.stub(console, 'error')
|
||||
errors.handle(error, value)
|
||||
expectations(processExitStub, logErrorStub)
|
||||
processExitStub.restore()
|
||||
logErrorStub.restore()
|
||||
|
||||
it 'should exit if the last parameter is true', ->
|
||||
error = new Error()
|
||||
checkProcessExitOption error, true, (processExitStub) ->
|
||||
expect(processExitStub).to.have.been.calledWith(1)
|
||||
|
||||
it 'should not exit if the last parameter is false', ->
|
||||
error = new Error()
|
||||
checkProcessExitOption error, false, (processExitStub) ->
|
||||
expect(processExitStub).to.not.have.been.called
|
||||
|
||||
it 'should handle a custom error exit code from the error instance', ->
|
||||
error = new Error()
|
||||
error.exitCode = 123
|
||||
checkProcessExitOption error, true, (processExitStub) ->
|
||||
expect(processExitStub).to.have.been.calledWith(123)
|
||||
|
||||
it 'should print stack trace if DEBUG is set', ->
|
||||
process.env.DEBUG = true
|
||||
error = new Error()
|
||||
checkProcessExitOption error, false, (processExitStub, logErrorStub) ->
|
||||
expect(logErrorStub).to.have.been.calledOnce
|
||||
expect(logErrorStub).to.have.been.calledWith(error.stack)
|
||||
delete process.env.DEBUG
|
||||
|
||||
it 'should handle EISDIR', ->
|
||||
error = new Error()
|
||||
error.code = 'EISDIR'
|
||||
error.path = 'hello'
|
||||
checkProcessExitOption error, false, (processExitStub, logErrorStub) ->
|
||||
expect(logErrorStub).to.have.been.calledOnce
|
||||
expect(logErrorStub).to.have.been.calledWith('File is a directory: hello')
|
||||
|
||||
it 'should handle ENOENT', ->
|
||||
error = new Error()
|
||||
error.code = 'ENOENT'
|
||||
error.path = 'hello'
|
||||
checkProcessExitOption error, false, (processExitStub, logErrorStub) ->
|
||||
expect(logErrorStub).to.have.been.calledOnce
|
||||
expect(logErrorStub).to.have.been.calledWith('No such file or directory: hello')
|
||||
|
||||
it 'should handle EACCES', ->
|
||||
error = new Error()
|
||||
error.code = 'EACCES'
|
||||
checkProcessExitOption error, false, (processExitStub, logErrorStub) ->
|
||||
expect(logErrorStub).to.have.been.calledOnce
|
||||
expect(logErrorStub.getCall(0).args[0]).to.match(/^You don\'t have enough privileges to run this operation./)
|
Reference in New Issue
Block a user