Compare commits

...

259 Commits

Author SHA1 Message Date
9260c8dce2 Resin CLI v2.0.1 2015-10-26 08:46:16 -04:00
bea5f22732 Merge pull request #251 from resin-io/jviotti/fix/stream-wait-sudo
Refer to the correct waiting stream function
2015-10-26 08:45:14 -04:00
363f12f81b Refer to the correct waiting stream function
We recently changed to using `rindle`, however looks like we forgot
to replace this particular instance.
2015-10-26 08:34:07 -04:00
1cbd33679f Resin CLI v2.0.0 2015-10-26 08:08:20 -04:00
e962371b59 Merge pull request #248 from resin-io/jviotti/remove/associate
Remove app associate command
2015-10-21 14:13:28 -04:00
77a3d4d6f6 Merge pull request #249 from resin-io/jviotti/fix/validation-module
Fix validation module require typo
2015-10-21 14:13:21 -04:00
fc5fe6cf68 Fix validation module require typo 2015-10-21 13:28:51 -04:00
f921488e8c Remove app associate command 2015-10-21 13:25:22 -04:00
8fe08642f5 Merge pull request #245 from resin-io/jviotti/feature/simplify-quickstart
Remove project directory creation in quickstart
2015-10-21 13:20:35 -04:00
822632718f Merge pull request #247 from resin-io/jvioti/refactor/os
Remove unused getOperatingSystem function
2015-10-21 11:43:19 -04:00
f66cd00646 Remove project directory creation in quickstart
The last part of `quickstart` feels weird. By consensus, we remove the
part that attempts to create a project directory and leave that step to
the user.
2015-10-21 11:18:29 -04:00
5498e45a35 Merge pull request #246 from resin-io/jviotti/refactor/rindle
Use rindle instead of custom waitStream
2015-10-21 11:15:12 -04:00
38479b3191 Merge pull request #244 from resin-io/jviotti/refactor/unused-npm
Remove unused npm dependency
2015-10-21 10:22:09 -04:00
965fd8fc19 Remove unused getOperatingSystem function 2015-10-21 10:20:38 -04:00
7a4f551a47 Use rindle instead of custom waitStream 2015-10-21 10:17:10 -04:00
303fd74012 Merge pull request #243 from resin-io/jviotti/feature/validation
Refactor validation to a single place
2015-10-21 10:11:46 -04:00
6a7d1b0c70 Remove unused npm dependency 2015-10-21 10:02:08 -04:00
c1e6a28640 Refactor validation to a single place 2015-10-21 09:37:25 -04:00
ec72f93480 Merge pull request #242 from resin-io/jviotti/feature/us-pw-login
Implement user/password login with 2FA support
2015-10-21 09:32:17 -04:00
8913fb515b Implement user/password login with 2FA support 2015-10-21 08:28:20 -04:00
6f91ff898f Merge pull request #241 from resin-io/jviotti/doc/drive
Regenerate os initialize documentation
2015-10-20 10:22:40 -04:00
aa949103f6 Merge pull request #240 from resin-io/jviotti/feature/device-init-advanced
Allow advanced option in device init
2015-10-20 09:47:04 -04:00
14bd4ad74e Regenerate os initialize documentation 2015-10-20 09:17:48 -04:00
f2507daa09 Allow advanced option in device init
This option is inherited to `os configure`.
2015-10-20 09:16:56 -04:00
ff81c1e514 Merge pull request #234 from resin-io/jviotti/feature/advanced-options
Ignore advanced configuration questions by default
2015-10-20 09:12:38 -04:00
7ce92b47a0 Merge pull request #238 from resin-io/jviotti/cleanup/dependencies
Remove unused resin-device-config
2015-10-19 16:04:50 -04:00
bde5cc65da Merge pull request #235 from resin-io/jviotti/fix/device-register-help
Load device info after all other device commands
2015-10-19 15:51:15 -04:00
ec9347c3a4 Merge pull request #237 from resin-io/jviotti/fix/hound
Change java_script to javascript in hound config
2015-10-19 15:51:07 -04:00
ceb8dada1d Remove unused resin-device-config 2015-10-19 15:26:09 -04:00
f4186acf80 Merge pull request #236 from resin-io/jviotti/feature/device-register-uuid
Allow passing a custom uuid to device register
2015-10-19 15:17:25 -04:00
05ce54eac1 Change java_script to javascript in hound config
They seemed to change this recently.
2015-10-19 14:21:10 -04:00
d28ecf3230 Allow passing a custom uuid to device register 2015-10-19 14:16:47 -04:00
8562f723c5 Load device info after all other device commands
This command obscures help pages for all device commands registered
afterwards since it's a common prefix for all of them.
2015-10-19 14:14:04 -04:00
f6d2043747 Merge pull request #232 from resin-io/jviotti/fix/app-create-validation
Require application name to have at least 4 characters
2015-10-19 14:06:43 -04:00
485818f3b5 Merge pull request #233 from resin-io/jviotti/fix/app-create-type
Fix --type option taking no effect in app create
2015-10-19 14:03:59 -04:00
ec28bd9c9e Ignore advanced configuration questions by default
The advanced questions can be enabled by passing `--advanced` in `os
configure`.
2015-10-19 14:02:57 -04:00
ad68dcf692 Fix --type option taking no effect in app create 2015-10-19 13:07:23 -04:00
0b7e2a2c8c Require application name to have at least 4 characters
We get a weird error message from pine otherwise:

	ResinRequestError: Request error: It is necessary that each app name
	that is of a user (Auth), has a Length (Type) that is greater than or
	equal to 4.
2015-10-19 10:56:02 -04:00
b6ebd0631a Merge pull request #229 from resin-io/jviotti/feature/os-initialize-type-option
Take device type as an option in os initialize
2015-10-15 09:59:44 -04:00
7ace4bdfa6 Merge pull request #230 from resin-io/jviotti/fix/update
Improve the way the update notifier is shown
2015-10-15 09:43:20 -04:00
1cfbd4197d Improve the way the update notifier is shown
Current has the following problems:

- Our custom message gets printed even if the notifier doesn't contain
an update.

- The notifier box is deferred, therefore it's printed at the end of the
command. Since our custom message is printed at the beginning, it makes
no sense at all.
2015-10-15 09:18:45 -04:00
b2425d2c0e Take device type as an option in os initialize 2015-10-15 09:11:38 -04:00
0ba914236a Merge pull request #228 from resin-io/jviotti/feature/os-initialize-drive
Support drive option in os initialize
2015-10-15 08:49:26 -04:00
71ee0a6cf7 Support drive option in os initialize
This allows the user to bypass the drive selection dialog.

This option can be used along with `--yes` to make the command
completely non-interactive. For example:

	$ resin os initialize rpi.img 'raspberry-pi' --drive /dev/disk2 --yes
2015-10-15 08:14:35 -04:00
4326ad4d9c Merge pull request #227 from resin-io/jviotti/fix/os-initialise-yes
Add missing `yes` option to `os initialize`
2015-10-15 07:44:33 -04:00
055bac6ff4 Merge pull request #226 from resin-io/jviotti/feature/223/update-sudo
Clarify the need for admin privileges on update
2015-10-15 07:40:15 -04:00
58713dc291 Add missing yes option to os initialize
This option is tried to be used within the command, but is not defined
as a formal Capitano option.
2015-10-14 17:49:27 -04:00
adf4aef517 Clarify the need for admin privileges on update
Fixes: https://github.com/resin-io/resin-cli/issues/223
2015-10-14 13:45:08 -04:00
a17bb2cc09 Merge pull request #220 from resin-io/jviotti/changelog/spacing
Remove spaces between lists in CHANGELOG
2015-10-13 13:04:08 -04:00
48d620f7cd Remove spaces between lists in CHANGELOG
GitHub displays the lists in a weird way otherwise.
2015-10-13 12:39:43 -04:00
fa7b104762 Resin CLI v1.1.0 2015-10-13 12:37:50 -04:00
faf2fa167f Merge pull request #219 from resin-io/jviotti/doc/troubleshoot
Remove outdated troubleshooting information
2015-10-13 12:17:46 -04:00
ad1e68427c Remove outdated troubleshooting information 2015-10-13 11:46:21 -04:00
e9147f0f6f Merge pull request #218 from resin-io/jviotti/fix/windows-edison
Avoid _.ary in temporal path disposer
2015-10-12 18:28:30 -04:00
cddf630907 Avoid _.ary in temporal path disposer
For some reason fails with a weird Bluebird error on Windows
2015-10-12 18:12:58 -04:00
494a286cae Merge pull request #217 from resin-io/jviotti/upgrade/sdk
Upgrade SDK to v3.0.0
2015-10-12 09:03:59 -04:00
e5e871ddcd Upgrade SDK to v3.0.0
Breaking changes in this version:

- `resin.models.device.generateUUID()` is now async.
2015-10-12 08:34:22 -04:00
5a3166e3de Merge pull request #215 from resin-io/jviotti/fix/await-wrap
Shorten the length of await device message
2015-10-07 12:41:57 -04:00
3149464c7a Shorten the length of await device message
If the spinner message doesn't fit in your terminal, each spinner
position will be printed in different lines.

We mitigate this by dramatically shortenning the message.
2015-10-07 11:38:59 -04:00
97d9b7816f Merge pull request #214 from resin-io/jviotti/feature/plugin-warn-red
Print plugin warnings in red as other errors
2015-10-06 19:18:33 -04:00
ec77437080 Print plugin warnings in red as other errors
For the sake of consistency.
2015-10-06 18:51:17 -04:00
d65882639a Merge pull request #211 from resin-io/jviotti/feature/101/help-topics
Separate general help per topic relevance
2015-10-02 09:12:58 -04:00
f8470287c1 Separate general help per topic relevance
Only list primary commands by default, unless a `--verbose` option is
passed to list the additional ones.

Fixes: https://github.com/resin-io/resin-cli/issues/101
2015-10-02 08:50:32 -04:00
3cc41ed62a Merge pull request #212 from resin-io/jviotti/fix/109/root-owner
Call os initialize as an elevated process
2015-10-01 14:08:19 -04:00
445e37ccaf Call os initialize as an elevated process
Currently, the fact that `os initialize` requires elevated permissions
forced us to require calling commands that reuse it, such as `device
init` and `quickstart` with administrator permissions as well.

This ended up causing issues like saving images in the cache that belong
to root, or initializing git repositories that requires `sudo` to
commit.

The solution is to call `os initialize` as a child process preppending
`sudo` within `device init`.

Fixes: https://github.com/resin-io/resin-cli/issues/109
2015-10-01 13:07:53 -04:00
ed6427c541 Merge pull request #210 from resin-io/jviotti/feature/os-init-type
Make os initialize take a device type instead of a uuid
2015-09-30 15:59:58 -04:00
90be01b05d Make os initialize take a device type instead of a uuid 2015-09-30 14:31:23 -04:00
1124293b9a Merge pull request #209 from resin-io/jviotti/doc/update
Regenerate documentation
2015-09-30 14:30:43 -04:00
0e804fdfd8 Regenerate documentation 2015-09-30 12:07:44 -04:00
0443a35f2b Merge pull request #207 from resin-io/jviotti/fix/promise-using
Fix incorrect Promise.using syntax
2015-09-30 12:06:51 -04:00
3a148217e0 Merge pull request #208 from resin-io/jviotti/fix/manifest-device-type
Send device type correctly to getManifestBySlug
2015-09-30 12:06:45 -04:00
79d1892b66 Send device type correctly to getManifestBySlug
Currently, we we're sending the wholea device object to
`getManifestBySlug`, which ended up in an unsupported device error.
2015-09-30 11:38:34 -04:00
0e06ac464f Fix incorrect Promise.using syntax
`Promise.using` takes the function that acts on the resource as the
second argument, instead of as `.then()`.
2015-09-30 11:37:27 -04:00
5ae83d8337 Merge pull request #206 from resin-io/jviotti/fix/uncompress-os-download
Uncompress zip packages in os download
2015-09-30 10:48:36 -04:00
8694ee2c59 Merge pull request #205 from resin-io/jviotti/feature/rimraf-file-disposer
Use rimraf for deleting os temporary files
2015-09-30 10:31:46 -04:00
8234f7675a Uncompress zip packages in os download
When downloading an operating system image, if the image is a zip
package, uncompress it automatically.
2015-09-30 10:16:24 -04:00
15cb0c4889 Use rimraf for deleting os temporary files
We already use `rimraf` for deleting os temporary directories, however
there are a few benefits of using it for files as well:

- Simplicity. We avoid having to check if a path is a file or directory.
- `rimraf` attempts to workaround the known Windows issues of anti
viruses not closing files. Described in more detail here: https://github.com/resin-io/resin-cli/blob/master/TROUBLESHOOTING.md#i-get-ebusy-errors-after-initializing-a-device-even-as-administrator-on-windows
2015-09-30 10:06:08 -04:00
a3ebd9827f Merge pull request #204 from resin-io/jviotti/fix/console.info
Use console.info in os download
2015-09-30 10:05:19 -04:00
30d84f015a Merge pull request #199 from resin-io/jviotti/feature/197/device-init-apps
Prompt for select application if running device init with no arguments
2015-09-29 15:31:59 -04:00
686414b03d Merge pull request #198 from resin-io/jviotti/feature/temp-disposer
Use Promise.disposer() to make sure temp files are deleted
2015-09-29 15:25:09 -04:00
6377618c12 Use console.info in os download
`console.info` calls can be quieted by the `--quiet` option.
2015-09-29 15:15:39 -04:00
f17e9c97b8 Prompt for select application if running device init with no arguments
Currently, if `device init` was ran without an application argument, we
attempted to get the application name from the current directory, given
it was a git repository.

This approach led to confusions from time to time, so now we prompt the
user to select one of it's own applications from a dropdown instead of
checking the current directory in this edge case.

Fixes: https://github.com/resin-io/resin-cli/issues/197
2015-09-29 15:10:59 -04:00
21fcdfaff6 Use Promise.disposer() to make sure temp files are deleted 2015-09-29 15:08:24 -04:00
4072edcced Merge pull request #203 from resin-io/jviotti/feature/os-initialize
Implement os initialize command
2015-09-29 14:59:59 -04:00
d704c10197 Implement os initialize command
This command initialized an operating system image with a disk device.
2015-09-29 14:52:34 -04:00
dea2a7f055 Merge pull request #202 from resin-io/jviotti/feature/device-register
Implement device register command
2015-09-29 14:41:03 -04:00
7e6eb4b9e4 Implement device register command
This command registers a new device with the passed application,
returning the new device uuid.
2015-09-29 14:33:31 -04:00
61474fba5c Merge pull request #201 from resin-io/jviotti/feature/os-configure
Implement os configure
2015-09-29 14:26:03 -04:00
42256384be Implement os configure
This command, given a path to an image and a device uuid, perform
configuration based on the resin-device-type manifests.
2015-09-29 13:47:10 -04:00
e25f232fe5 Merge pull request #200 from resin-io/jviotti/feature/os-download
Implement os download command
2015-09-29 13:11:24 -04:00
f6d8f12ba2 Implement os download command
This command download an unconfigured image to both the cache and to the
specified location by the `--output` option.
2015-09-29 13:03:14 -04:00
d2b9e6fd8c Merge branch 'jviotti/doc/anti-virus-ebusy' 2015-09-21 09:28:13 -04:00
cf7effacc0 Document anti virus and EBUSY errors on Windows 2015-09-21 09:27:18 -04:00
1eb7aba293 Merge branch 'master' of https://github.com/resin-io/resin-cli 2015-09-21 09:25:57 -04:00
21e916679c Update author email to @resin.io 2015-09-21 09:15:27 -04:00
d574743c21 Merge pull request #195 from resin-io/jviotti/docs/cygwin-to-troubleshooting
Move Cygwin caveat from README to TROUBLESHOOTING
2015-09-21 08:57:39 -04:00
d8a2b82662 Move Cygwin caveat from README to TROUBLESHOOTING 2015-09-21 08:51:54 -04:00
f4ebd890df Merge pull request #194 from resin-io/jviotti/doc/troubleshooting
Add Troubleshooting guide
2015-09-21 08:47:14 -04:00
b0be5de83a Add Troubleshooting guide
This guide described common Resin CLI issues and how to fix them.
2015-09-21 08:32:11 -04:00
ba21ddd010 Merge pull request #192 from resin-io/jviotti/doc/readme
Improve README.md
2015-09-21 08:01:44 -04:00
eecd7a0fd8 Improve README.md
- Make user oriented instead of developer oriented.
- Remove deprecated information.
- Add Requisites section.
- Add Getting Started section.
- Add Support section.
- Add License section.
2015-09-21 07:56:24 -04:00
eb4c2f62a7 Resin CLI v1.0.0 2015-09-11 21:21:44 +03:00
dc1e5e6512 Merge pull request #190 from resin-io/jviotti/message/await
Improve device awaiting message
2015-09-11 19:19:14 +03:00
adc0b183cd Improve device awaiting message
Current message sounds too robotic.
2015-09-11 19:13:30 +03:00
828b4f73d1 Fix selecting existing application in quickstart 2015-09-11 18:30:30 +03:00
82a0761f49 Merge pull request #189 from resin-io/jviotti/feature/error-highlight
Highlight errors in red
2015-09-11 14:59:14 +03:00
904b9f07fb Highlight errors in red
- Move error translation logic to resin-io/resin-cli-errors.
- Force `process.exit()`.
2015-09-11 14:47:38 +03:00
3c0acaa7da Merge pull request #188 from resin-io/jviotti/feature/device-specs
Implement device specs. Fix #99
2015-09-11 13:10:44 +03:00
64c8420c9d Implement device specs. Fix #99
Support for all devices. Tested in the following ones:

- Intel Edison.
- Raspberry Pi 2.
- Parallella.
2015-09-11 13:02:59 +03:00
26e3dc1aa7 Merge pull request #187 from resin-io/jviotti/feature/ignore-resinrc
Add resinrc.yml to gitignore
2015-09-08 09:31:40 +03:00
3bc22a7b7c Merge pull request #186 from resin-io/jviotti/refactor/settings
Use settings from the SDK during login
2015-09-08 09:16:05 +03:00
7e5b2695fe Add resinrc.yml to gitignore
Some modules have an use case for shipping the configuration file with
the project, however this is not the case for the CLI.
2015-09-08 09:07:48 +03:00
79afa79fd9 Use settings from the SDK during login
This enforces all clients to use the Resin Settings Client version that
the SDK provides, reducing incompatibilities caused by different modules
requiring different Resin Settings Client versions.
2015-09-08 09:06:03 +03:00
cdaaddb826 Merge pull request #185 from resin-io/jviotti/upgrade/sdk
Upgrade Resin SDK to v2.7.2
2015-09-07 11:45:25 +03:00
ec5f6a7cd8 Upgrade Resin SDK to v2.7.2 2015-09-07 11:25:01 +03:00
72f34031a9 Merge pull request #184 from resin-io/jviotti/fix/token-auth
Check token validity against the API when login
2015-09-05 20:48:22 +03:00
dc257b5cab Check token validity against the API when login
Consider the following case:

The SDK is configured to point to staging, but the user passes a token
from production, or viceversa. Since the token is valid in a sense that
is valid JWT and contains real data, the CLI will report as a success.

The user will then get Unauthorized errors when using the API.
2015-09-05 20:17:34 +03:00
4bdcd3d2ee Merge pull request #180 from resin-io/issue_#103
Resin CLI Events integration. Fix #103
2015-09-05 20:11:52 +03:00
b0650530cc Resin CLI Events integration. Fix #103 2015-09-05 19:15:31 +03:00
454669ada2 Merge pull request #183 from resin-io/jviotti/182/docs/update
Update documentation
2015-09-01 13:09:42 -04:00
a65975596e Merge pull request #181 from resin-io/reword-example
Replace device name with uuid in env-variables command example
2015-09-01 13:03:27 -04:00
91f878cfc0 Update documentation
- There were changes to commands.
- Regenerates `login` documentation with production url.

Fixes: https://github.com/resin-io/resin-cli/issues/182
2015-09-01 13:01:47 -04:00
8c3e832cdc Replace device name with uuid in env-variables command example 2015-09-01 00:56:01 +03:00
a5c670902d Merge pull request #179 from resin-io/jviotti/appveyor/cache
Add Appveyor cache support
2015-08-28 08:42:57 -04:00
14be1d5f9f Add Appveyor cache support 2015-08-28 08:22:26 -04:00
41d6d5c670 Merge pull request #178 from resin-io/jviotti/refactor/quickstart
Refactor quickstart
2015-08-27 10:13:56 -04:00
a090e6c21d Refactor quickstart
- Use promises.
- Move some logic to `helpers`.
- Inline `device await` command.
2015-08-27 10:01:33 -04:00
d78c1ffa47 Merge pull request #176 from resin-io/jviotti/cleanup/compiled-files
Remove orphaned files from build/
2015-08-27 09:03:38 -04:00
5808d20d88 Merge pull request #177 from resin-io/modify-capitanodoc
Update capitanodoc.json
2015-08-27 15:45:05 +03:00
7f4065e3da Update capitanodoc.json 2015-08-27 15:30:38 +03:00
07daa51051 Remove orphaned files from build/
Some files were deleted from `lib/` but still live in `build/`. More
specifically:

- `build/actions/update.js`.
- `build/data/`.
2015-08-27 08:29:22 -04:00
d10d4ce185 Merge pull request #174 from resin-io/jviotti/feature/confirm-abortion
Add `Aborted` error message when not accepting a confirmation
2015-08-24 08:47:29 -04:00
7f763e7881 Merge pull request #175 from resin-io/jviotti/fix/update-root
Don't check for available updates when running as root
2015-08-24 07:41:56 -04:00
5de0f66d7a Don't check for available updates when running as root
`update-notifier` persist its update check results in a file, which is
then read when running again the application.

If this file gets written when the application is being run as root, we
get ugly EPERM issues.
2015-08-20 16:54:22 -04:00
354921ca92 Add Aborted error message when not accepting a confirmation
This prevents a lot of duplicate code to check for confirmation status
and exit from the current action.
2015-08-20 16:16:20 -04:00
fb28cc7d2f Merge pull request #173 from resin-io/jviotti/upgrade/form
Upgrade Resin CLI Form to v1.2.1
2015-08-20 16:11:17 -04:00
7cf89a9bf6 Merge pull request #172 from resin-io/jviotti/cleanup/readme
Remove Man pages section from README.md
2015-08-20 16:11:09 -04:00
a3cbc549d8 Upgrade Resin CLI Form to v1.2.1
This version contains a fix for a bug that prevented `when` properties
from working as expected.
2015-08-20 15:55:57 -04:00
1c48328347 Remove Man pages section from README.md
Man pages are not longer being produced.
2015-08-20 12:04:11 -04:00
9b69fe3c3c Merge pull request #171 from resin-io/jviotti/upgrade/cli-form
Update Resin CLI Form to v1.2.0
2015-08-20 12:02:26 -04:00
dc513a08f6 Update Resin CLI Form to v1.2.0
This version includes support for the `drive` input type.
2015-08-20 11:55:53 -04:00
fcc44949a7 Merge pull request #169 from resin-io/jviotti/refactor/plugins
Upgrade Nplugm to v3.0.0
2015-08-19 12:49:32 -04:00
006764af66 Merge pull request #170 from resin-io/jviotti/cleanup-resin-write
Remove unused resin-write bin script
2015-08-19 12:49:25 -04:00
b879d3f9ea Remove unused resin-write bin script
This script was used along with windosu as a workaround to call `device
init` with elevated permissions.

Since Windows elevation is not used anymore for now, this script can be
removed.
2015-08-19 11:30:48 -04:00
7f4863da86 Upgrade Nplugm to v3.0.0
This new version supports promises and contains speed improvements.
2015-08-19 11:27:28 -04:00
ba6f50d171 Merge pull request #168 from resin-io/jviotti/cleanup/plugins
Remove plugins manipulation commands
2015-08-19 11:14:35 -04:00
a803d4f646 Remove plugins manipulation commands
Since we're now forcing users to rely on `npm` directly for updates, we
can also get rid of plugin commands that attempt to
install/update/remove using npm programatically and require users to use
`npm` directly as well.

This commit removes the following commands:

- `plugins`
- `plugin install`
- `plugin update`
- `plugin remove`

Despite plugin related commands being removed, *the functionality that
scans for plugins and registers them remains intact*.
2015-08-19 10:57:42 -04:00
85d940df66 Merge pull request #165 from resin-io/jviotti/feature/update-notifier
Notify the user if there is an available update
2015-08-19 07:56:40 -04:00
6f9535ca34 Merge pull request #167 from resin-io/issue-166
Display msg when app/device does not have env variables. Fix #166.
2015-08-18 18:17:21 +03:00
019e2ac357 Display msg when app/device does not have env variables. Fix #166 2015-08-18 18:12:08 +03:00
433916e18a Merge pull request #164 from resin-io/issue-90
Add message informing the user about potential delay in system img initialization. Fix #90.
2015-08-18 16:04:44 +03:00
f19588032f Notify the user if there is an available update
For this we use the `update-notifier` module with its default settings.

This module will print a nice banner prompting the user to run the
corresponding npm command to update.
2015-08-18 08:53:06 -04:00
0595452c3d Add message informing the user about potential delay in system img initialization. Fix #90. 2015-08-18 15:43:52 +03:00
d6305df48e Merge pull request #163 from resin-io/issue-108
Reword ending message in quickstart. Fix #108
2015-08-18 15:00:38 +03:00
f7084580b2 Merge pull request #162 from resin-io/issue-106
Reword output during download in device init. Fix #106.
2015-08-18 14:59:08 +03:00
3dd5f5858a Reword ending message in quickstart. Fix #108 2015-08-18 14:34:15 +03:00
02a06e1e7c Reword output during download in device init. Fix #106. 2015-08-18 13:55:04 +03:00
50daf8ef73 Merge pull request #161 from resin-io/jviotti/refactor/env
Refactor env action module to use promises
2015-08-17 10:42:40 -04:00
fd5a34a1c4 Refactor env action module to use promises 2015-08-17 10:32:22 -04:00
51fda13684 Merge pull request #160 from resin-io/jviotti/remove/devices-supported
Remove `devices supported` command
2015-08-17 10:22:10 -04:00
a698b25fda Remove devices supported command
The command is not necessary and unused.
2015-08-17 10:05:36 -04:00
89bd861d8e Merge pull request #159 from resin-io/jviotti/refactor/device
Refactor device actions to use promises
2015-08-17 10:03:57 -04:00
e5b7aae4ae Refactor device actions to use promises 2015-08-17 09:49:59 -04:00
031168ceed Merge pull request #158 from resin-io/jviotti/refactor/keys
Refactor keys action to use promises
2015-08-17 09:46:53 -04:00
09a5788902 Refactor keys action to use promises 2015-08-17 09:32:05 -04:00
713664d103 Merge pull request #157 from resin-io/jviotti/feature/settings-projects-dir
Make use of `projectsDirectory` SDK setting in Quickstart
2015-08-17 09:20:30 -04:00
f63391acf9 Make use of projectsDirectory SDK setting in Quickstart
We were currently building this path ourselves, hardcoding the place of
the resin local per user directory instead of relying on the foundations
that `resin-settings-client` give us.
2015-08-17 09:06:27 -04:00
79ee4302ec Merge pull request #156 from resin-io/jviotti/upgrade/sdk
Upgrade Resin SDK to v2.4.1
2015-08-17 08:59:17 -04:00
9adda22921 Upgrade Resin SDK to v2.4.1
This new version contains fixes for the following issues:

- https://github.com/resin-io/resin-cli/issues/87
- https://github.com/resin-io/resin-cli/issues/120
2015-08-17 08:42:17 -04:00
25311f2a18 Merge pull request #146 from resin-io/jviotti/refactor/auth
Refactor auth actions to use promises
2015-08-17 08:41:19 -04:00
70c060b124 Refactor auth actions to use promises 2015-08-17 08:22:48 -04:00
7a8a3c851b Merge pull request #138 from resin-io/refactor/help
Refactor help module
2015-08-17 08:02:15 -04:00
1096b2d212 Merge pull request #143 from resin-io/jviotti/refactor/app
Refactor app actions to use promises
2015-08-17 08:02:04 -04:00
ee286c5690 Merge pull request #144 from resin-io/jviotti/refactor/note
Refactor note set command to use promises
2015-08-17 08:01:47 -04:00
1da1d2e6fc Merge pull request #152 from resin-io/jviotti/fix/151/ssh-key-list
Print ssh key separately from the information table
2015-08-17 08:01:32 -04:00
64be9f936d Merge pull request #155 from resin-io/jviotti/feature/107/await-spinner
Implement a spinner when awaiting for a device. Fix #107
2015-08-17 08:01:22 -04:00
30f24333c0 Implement a spinner when awaiting for a device. Fix #107
Fixes:

- https://github.com/resin-io/resin-cli/issues/107
2015-08-14 14:35:38 -04:00
30f6a78282 Merge pull request #154 from resin-io/jviotti/fix/device-await
Fix broken device await command
2015-08-14 14:31:00 -04:00
8c9a0e0ff1 Fix broken device await command
There were two issues that prevented this command from working
correctly:

1- `Promise.delay()` is used, but `Promise` was not imported.
2- The following line had incorrect indentation (spaces instead of
		tabs):

		poll().nodeify(done)

Therefore CoffeeScript interpreted that the line had to be executed at
the end of the `poll()` function, causing `poll()` to never be called.
2015-08-14 14:11:49 -04:00
8268bbf700 Merge pull request #150 from resin-io/jviotti/upgrade/sdk
Upgrade Resin SDK to v2.4.0
2015-08-14 12:42:31 -04:00
e712e2f266 Print ssh key separately from the information table
Since the public key string is long, it might wrap to lines below,
causing the table layout to break.

A quick solutio is to print the ssh key after the table.

Fixes:

- https://github.com/resin-io/resin-cli/issues/151
2015-08-14 12:25:55 -04:00
0807b6a2d9 Upgrade Resin SDK to v2.4.0
This release fixes:

- Check if device exists before removing it "resin device rm <uuid>"
	- https://github.com/resin-io/resin-cli/issues/123

- Check if app exists before removing it "resin app rm <appName>"
	- https://github.com/resin-io/resin-cli/issues/114

- Command does not display correct output "resin key <id>"
	- https://github.com/resin-io/resin-cli/issues/112

Since it includes the following PRs:

- https://github.com/resin-io/resin-sdk/pull/103
- https://github.com/resin-io/resin-sdk/pull/107
2015-08-14 12:17:59 -04:00
83382cc8f7 Merge pull request #145 from resin-io/jviotti/fix/logs-help-indentation
Fix logs command help string indentation
2015-08-14 08:27:38 -04:00
8401aaeae2 Merge pull request #149 from resin-io/jviotti/fix/111/email-validation
Validate that email address is valid during signup. Fix #111
2015-08-14 07:58:40 -04:00
abf5740950 Merge pull request #148 from resin-io/jviotti/fix/14/logs-history
Force logs command to exit when not in --tail mode. Fix #14.
2015-08-14 07:58:05 -04:00
606777508d Merge pull request #147 from resin-io/jviotti/cleanup/preferences
Remove preferences command
2015-08-14 07:57:14 -04:00
e9ec6c67b2 Validate that email address is valid during signup. Fix #111
For this we use a third party dependency from npm called `valid-email`
to avoid hardcoding and having to mantain a regular expression.
2015-08-13 15:22:22 -04:00
69566f7fc3 Force logs command to exit when not in --tail mode. Fix #14.
PubNub keeps the process alive after a history query for some reason, so
trying to print the logs history like:

	$ resin logs <uuid>

Will result in the logs being printed correctly, but the process waiting
infinitely without ending.

The workaround consists in forcing `process.exit` to exit the process
with an error code zero.

Caveats:

- This workaround prevents this command to be used programatically.

Issue: https://github.com/resin-io/resin-cli/issues/14
2015-08-13 15:08:16 -04:00
6e4b299c7d Remove preferences command 2015-08-13 15:00:51 -04:00
ef35ebf79d Fix logs command help string indentation
For some reason it was indented a few times unnecesarily.
2015-08-13 14:33:19 -04:00
1bc78edf71 Refactor help module
Main changes:

- Use the `columnify` module to display the commands instead of using
manual parsing.

- Extract logic to create a string representation from an option
signature to Capitano, and reuse here.

See https://github.com/resin-io/capitano/pull/28

Some bugs were caught and fixes during the refactoring:

- In command help, if the command didn't exist, we reused default
Capitanos command not found function which uses `process.exit(1)`. This
was changed to pass a custom error to `done()`, so the command fails
correctly when using programatically.

- General help didn't call `done()` at all, thus causing problems if
using the command programatically someday.
2015-08-13 14:19:07 -04:00
d5204a09f7 Refactor note set command to use promises 2015-08-13 14:17:02 -04:00
4647aa70c0 Implement utils/helpers to abstract common app patterns
- Add helpers.confirm() to abstract the process of asking for
confirmation.
- Add helpers.selectDeviceType() to abstract the form needed to ask for
device types.

The functions on this module are reused by app actions.
2015-08-13 14:04:47 -04:00
2e8ec3ac64 Merge pull request #142 from resin-io/jviotti/cleanup/dependencies
Remove unused dependencies imports from various files
2015-08-13 13:51:22 -04:00
25c6246e9f Refactor app actions to use promises
Use promises instead of `async` internally inside the following
commands:

- app create.
- app remove.
- app associate.
2015-08-13 13:42:49 -04:00
5408938007 Merge pull request #141 from resin-io/jviotti/cleanup/shell-completion
Remove command shell completion script
2015-08-13 13:08:08 -04:00
50cb04b6f7 Remove unused dependencies imports from various files 2015-08-13 13:04:22 -04:00
09fe4b11ad Merge pull request #139 from resin-io/jviotti/feature/drive-widget
Use Visuals drive widget in device init
2015-08-13 12:35:41 -04:00
4157f21e06 Merge pull request #140 from resin-io/jviotti/cleanup/elevate
Remove outdated Windows elevation mechanism
2015-08-13 12:23:26 -04:00
4900230881 Remove command shell completion script
This feature is unused and doesn't provide much value.

In the future, we may include autocompletion support built in the CLI.
2015-08-13 12:21:55 -04:00
e60c0605e5 Use Visuals drive widget in device init
- Replace custom `drivelist` logic in "device init" with the new `drive`
widget.
2015-08-13 11:56:16 -04:00
085781fa18 Upgrade Resin CLI Visuals to v1.1.0
This version contains the `drive` widget.
2015-08-13 11:55:31 -04:00
08ad8bd5d7 Merge pull request #137 from resin-io/cleanup/slim
Slim down unused functionality
2015-08-13 11:52:57 -04:00
3d36e5f5d3 Remove outdated Windows elevation mechanism
This functionality is outdated and not using anymore due to limitations
in the way it was addressed.

The module and dependencies are removed for now, and will be added back
in the future, once a better approach is planned.
2015-08-13 11:45:50 -04:00
57319f26a6 Slim down unused functionality 2015-08-12 08:17:46 -04:00
11033683fd Stop supporting iojs 2015-08-06 12:12:44 -04:00
e5166c6c8e Use Install-Product instead of Update-NodeJsInstallation 2015-08-06 11:46:57 -04:00
5c96663d1e Merge pull request #134 from resin-io/remove/drive-command
Remove drive command
2015-08-05 12:24:25 -04:00
ffb48c8669 Merge pull request #135 from resin-io/remove/examples-commands
Remove examples commands
2015-08-05 07:04:03 -04:00
12145a2393 Merge pull request #132 from resin-io/upgrade_travis
Test node v0.12 and io.js
2015-08-05 07:03:51 -04:00
f379866c1c Merge pull request #67 from resin-io/feature/wizard
Implement Quickstart command
2015-08-04 13:19:37 -04:00
dc030f4cd1 Implement Quickstart command 2015-08-04 20:16:55 +03:00
b726a2d778 Remove examples commands 2015-08-04 10:00:09 -04:00
a715ec9dc1 Remove drive command 2015-08-04 09:57:59 -04:00
d24b871964 Merge pull request #131 from resin-io/remove/selfupdate
Remove selfupdate functionality
2015-08-04 09:53:48 -04:00
bd0c4e6034 Merge pull request #133 from resin-io/upgrade_appveyor
Add io.js testing in Appveyor
2015-08-04 09:27:36 -04:00
95637e5608 Test node v0.12 and io.js 2015-08-04 14:15:30 +03:00
93b394ed76 Add io.js testing in Appveyor 2015-08-04 13:26:13 +03:00
b515e427ff Merge pull request #129 from resin-io/issue_#73
Add email address to the returned information, when using whoami(). Fix #73.
2015-08-03 16:14:47 -04:00
26b1acf5ef Merge pull request #130 from resin-io/remove/man
Remove man pages
2015-08-03 16:14:23 -04:00
f31eb7c2b5 Add email address to the returned information, when using whoami(). Fix #73. 2015-08-03 21:24:22 +03:00
d423a6ea24 Remove selfupdate functionality
We added this because we thought that knowledge of the supported device types, along with the configuration procedures was going to be encoded in the CLI.

With device specs, this is not longer the case.
2015-08-03 12:20:42 -04:00
4211333e4e Remove man pages 2015-08-03 12:08:49 -04:00
f220e380a7 Merge pull request #125 from resin-io/issue_#117
Display correctly the newly-created application id. Fix #117
2015-07-29 21:43:31 +03:00
9564b4e478 Display correctly the newly-created application id. Fix #117 2015-07-29 21:17:21 +03:00
e2125b8ce9 Fix #73 2015-07-29 21:15:29 +03:00
0bb0e6ea4b Merge pull request #124 from resin-io/integrate-new-resin-visuals-functionality
Integrate new resin-cli-visuals functionality
2015-07-29 09:43:24 -04:00
cf512cc01b Integrate new resin-cli-visuals functionality 2015-07-29 16:34:31 +03:00
fe3e68c3af Merge pull request #121 from resin-io/modify-function-description
Replace device name with uuid, found in resin envs examples in enviroment-variables
2015-07-28 08:49:19 -04:00
0bbfbe36c7 Replace device name with uuid, found in resin envs examples in enviroment-variables 2015-07-27 22:49:11 +03:00
d6ae689593 Merge pull request #118 from resin-io/resin-cli-form
Integrate resin-cli-form
2015-07-27 13:30:48 -04:00
5b5d1be52f Integrate resin-cli-form 2015-07-27 19:50:47 +03:00
cb808869dd Merge pull request #110 from resin-io/support_promises
Add promise support for Resin-SDK dependency
2015-07-24 08:02:40 -04:00
64d83dccfb Add promise support for Resin-SDK dependency 2015-07-24 00:24:17 +03:00
122253bb25 Merge pull request #95 from resin-io/fix/device-registered-at
Add registered_at UNIX epoch
2015-07-23 13:00:29 -04:00
1d53db2854 Add registered_at UNIX epoch 2015-07-23 12:47:49 -04:00
30dc5ea1ea Merge pull request #97 from resin-io/upgrade/dependencies
Upgrade dependencies
2015-07-15 09:08:35 -04:00
58c7ff1f1b Upgrade dependencies 2015-07-15 09:01:50 -04:00
57b9d634be Merge pull request #96 from resin-io/upgrade-visuals
Upgrade resin-cli-visuals dependency
2015-07-14 15:12:54 -04:00
6fb25dea88 Upgrade resin-cli-visuals dependencies 2015-07-14 18:18:19 +03:00
10478e389a Merge pull request #94 from resin-io/upgrade_vcs_dependencies
Upgrade Resin VCS dependencies
2015-07-13 07:37:02 -04:00
46fa4ee2a2 Upgrade Resin VCS dependencies 2015-07-11 00:03:20 +03:00
a7cefe44bf Merge pull request #86 from resin-io/feature/remove-selfupdate
Remove selfupdate functionality. Notify in all cases.
2015-07-09 13:45:09 -04:00
3d03585318 Merge pull request #93 from resin-io/feature/last_seen-undefined
Default device.last_seen to 'Not seen'. Closes #84.
2015-07-09 13:44:59 -04:00
076c3428ee Upgrade Resin CLI Visuals to v0.3.2 2015-07-09 13:43:20 -04:00
9d4ac46985 Default device.last_seen to 'Not seen'. Closes #84. 2015-07-09 09:56:39 -04:00
24edb867fa Upgrade Resin CLI Visuals to v0.3.1 2015-07-09 09:46:08 -04:00
e926ac46c9 Remove selfupdate functionality. Notify in all cases. 2015-07-09 08:18:06 -04:00
9ffd657dbb Merge pull request #85 from resin-io/feature/regenerate-docs
Regenerate docs
2015-07-07 18:40:40 -04:00
aa3cb39551 Regenerate docs 2015-07-07 18:01:25 -04:00
0acbdac66f Use NPM 2.12.1 in Appveyor due to a bug in 2.12.0 2015-07-02 14:28:19 -04:00
5619bdbb67 Resin CLI v0.11.1 2015-06-15 10:09:10 -04:00
381e63bfc9 Merge pull request #70 from resin-io/refactor/new-visuals
Upgrade Resin CLI Visuals and use it's new capabilities
2015-06-12 09:47:30 -04:00
e779347ff2 Merge pull request #69 from resin-io/feature/no-chop-key
Don't chop SSH key
2015-06-11 12:48:16 -04:00
8fa906dd48 Upgrade Resin CLI Visuals and use it's new capabilities 2015-06-11 12:46:56 -04:00
7e5ecd634d HOTFIX: isLoggedIn now returns a possible error 2015-06-11 12:40:49 -04:00
29cf4c1e89 Don't chop SSH key 2015-06-11 08:08:45 -04:00
73736abdea Merge pull request #68 from resin-io/feature/config
Implement config command
2015-06-11 07:57:31 -04:00
ef33156de7 Implement config command 2015-06-10 12:34:42 -04:00
75 changed files with 2113 additions and 3553 deletions

1
.gitignore vendored
View File

@ -33,3 +33,4 @@ release/build
npm-shrinkwrap.json
.resinconf
resinrc.yml

View File

@ -1,5 +1,5 @@
coffee_script:
config_file: coffeelint.json
java_script:
javascript:
enabled: false

View File

@ -1,4 +1,5 @@
language: node_js
node_js:
- "0.12"
- "0.11"
- "0.10"

74
CHANGELOG.md Normal file
View 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

View File

@ -1,4 +1,5 @@
# Resin CLI
Resin CLI
=========
[![npm version](https://badge.fury.io/js/resin-cli.svg)](http://badge.fury.io/js/resin-cli)
[![dependencies](https://david-dm.org/resin-io/resin-cli.png)](https://david-dm.org/resin-io/resin-cli.png)
@ -7,60 +8,49 @@
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
View 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).

View File

@ -4,6 +4,10 @@
init:
- git config --global core.autocrlf input
cache:
- C:\Users\appveyor\.node-gyp
- '%AppData%\npm-cache'
# what combinations to test
environment:
matrix:
@ -12,8 +16,8 @@ environment:
- nodejs_version: 0.12
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- npm -g install npm@2
- 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

View File

@ -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);
});

View File

@ -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,31 +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) {
options.type = type;
return resin.models.application.create(params.name, options.type, callback);
}, function(applicationId, callback) {
console.info("Application created: " + params.name + " (" + options.type + ", id " + applicationId + ")");
return 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);
}
};
@ -61,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);
}
};
@ -77,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);
}
};
@ -94,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);
}
};
@ -105,79 +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\nNotice this command asks for confirmation interactively.\nYou can avoid this by passing the `--yes` boolean option.\n\nExamples:\n\n $ resin app associate MyApp\n $ resin app associate MyApp --project my/app/directory',
options: [commandOptions.yes],
permission: 'user',
action: function(params, options, done) {
var currentDirectory;
currentDirectory = process.cwd();
return async.waterfall([
function(callback) {
return resin.models.application.has(params.name, callback);
}, function(hasApp, callback) {
var message;
if (!hasApp) {
return callback(new Error("Invalid application: " + params.name));
}
message = "Are you sure you want to associate " + currentDirectory + " with " + params.name + "?";
return visuals.patterns.confirm(options.yes, message, callback);
}, function(confirmed, callback) {
if (!confirmed) {
return done();
}
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);
}
};

View File

@ -1,48 +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('dashboardUrl'), '/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) {
return async.waterfall([
function(callback) {
if (params.token != null) {
return callback(null, params.token);
}
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 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 visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback);
});
}, function(token, callback) {
return resin.auth.loginWithToken(token, callback);
}, function(callback) {
return resin.auth.whoami(callback);
}, function(username, callback) {
console.info("Successfully logged in as: " + username);
return callback();
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);
}
};
@ -52,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);
}
};

View File

@ -1,33 +1,27 @@
(function() {
var _, async, capitano, commandOptions, fse, image, inject, manager, path, pine, registerDevice, resin, tmp, vcs, visuals;
var Promise, _, capitano, commandOptions, events, form, helpers, patterns, resin, rimraf, tmp, visuals;
fse = require('fs-extra');
Promise = require('bluebird');
capitano = require('capitano');
capitano = Promise.promisifyAll(require('capitano'));
_ = require('lodash-contrib');
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');
manager = require('resin-image-manager');
events = require('resin-cli-events');
image = require('resin-image');
rimraf = Promise.promisify(require('rimraf'));
inject = require('resin-config-inject');
patterns = require('../utils/patterns');
registerDevice = require('resin-register-device');
helpers = require('../utils/helpers');
pine = require('resin-pine');
tmp = require('tmp');
tmp = Promise.promisifyAll(require('tmp'));
tmp.setGracefulCleanup();
@ -39,49 +33,77 @@
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(null, devices);
});
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);
}
};
@ -91,198 +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 and other logging information by passing the `--quiet` boolean option.\n\nYou need to configure the network type and other settings:\n\nEthernet:\n You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".\n\nWifi:\n You can setup the device OS to use wifi by setting the `--network` option to "wifi".\n If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.\n\nYou can omit network related options to be asked about them interactively.\n\nExamples:\n\n $ resin device init\n $ resin device init --application MyApp\n $ resin device init --application MyApp --network ethernet\n $ resin device init /dev/disk2 --application MyApp --network wifi --ssid MyNetwork --key secret',
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',
root: true,
primary: true,
action: function(params, options, done) {
var networkOptions;
networkOptions = {
network: options.network,
wifiSsid: options.ssid,
wifiKey: options.key
};
return async.waterfall([
function(callback) {
if (options.application != null) {
return callback(null, options.application);
}
return vcs.getApplicationName(process.cwd(), callback);
}, function(applicationName, callback) {
options.application = applicationName;
return resin.models.application.has(options.application, callback);
}, function(hasApplication, callback) {
if (!hasApplication) {
return callback(new Error("Invalid application: " + options.application));
}
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();
}
if (networkOptions.network != null) {
return callback();
}
return visuals.patterns.selectNetworkParameters(function(error, parameters) {
if (error != null) {
return callback(error);
}
_.extend(networkOptions, parameters);
return callback();
});
}, function(callback) {
console.info("Checking application: " + options.application);
return resin.models.application.get(options.application, callback);
}, function(application, callback) {
return async.parallel({
manifest: function(callback) {
console.info('Getting device manifest for the application');
return resin.models.device.getManifestBySlug(application.device_type, callback);
},
config: function(callback) {
console.info('Fetching application configuration');
return resin.models.application.getConfiguration(options.application, networkOptions, callback);
}
}, callback);
}, function(results, callback) {
console.info('Associating the device');
return registerDevice.register(pine, results.config, function(error, device) {
if (error != null) {
return callback(error);
}
results.config.deviceId = device.id;
results.config.uuid = device.uuid;
params.uuid = results.config.uuid;
return callback(null, results);
});
}, function(results, callback) {
var bar, spinner;
console.info('Configuring device operating system image');
if (process.env.DEBUG) {
console.log(results.config);
}
bar = new visuals.widgets.Progress('Downloading Device OS');
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)');
return manager.configure(results.manifest, results.config, function(error, imagePath, removeCallback) {
spinner.stop();
return callback(error, imagePath, removeCallback);
}, function(state) {
if (state != null) {
return bar.update(state);
} else {
return spinner.start();
}
});
}, function(configuredImagePath, removeCallback, callback) {
var bar;
console.info('Attempting to write operating system image to drive');
bar = new visuals.widgets.Progress('Writing Device OS');
return image.write({
device: params.device,
image: configuredImagePath,
progress: _.bind(bar.update, bar)
}, function(error) {
if (error != null) {
return callback(error);
}
return callback(null, configuredImagePath, removeCallback);
});
}, function(temporalImagePath, removeCallback, callback) {
console.info('Image written successfully');
return removeCallback(callback);
}, function(callback) {
return resin.models.device.getByUUID(params.uuid, callback);
}, function(device, callback) {
console.info("Device created: " + device.name);
return callback(null, device.name);
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);
}
};

View File

@ -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);

View File

@ -1,20 +1,24 @@
(function() {
var _, async, commandOptions, resin, visuals;
var Promise, _, commandOptions, events, patterns, resin, visuals;
async = require('async');
Promise = require('bluebird');
_ = require('lodash-contrib');
_ = 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\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 MyDevice',
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.optionalApplication, commandOptions.optionalDevice, {
signature: 'verbose',
@ -25,25 +29,25 @@
],
permission: 'user',
action: function(params, options, done) {
return async.waterfall([
function(callback) {
if (options.application != null) {
return resin.models.environmentVariables.getAllByApplication(options.application, callback);
} else if (options.device != null) {
return resin.models.environmentVariables.device.getAll(options.device, callback);
} else {
return callback(new Error('You must specify an application or device'));
}
}, function(environmentVariables, callback) {
var isSystemVariable;
if (!options.verbose) {
isSystemVariable = resin.models.environmentVariables.isSystemVariable;
environmentVariables = _.reject(environmentVariables, isSystemVariable);
}
console.log(visuals.widgets.table.horizontal(environmentVariables, ['id', 'name', 'value']));
return callback();
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');
}
], done);
}).tap(function(environmentVariables) {
var isSystemVariable;
if (_.isEmpty(environmentVariables)) {
throw new Error('No environment variables found');
}
if (!options.verbose) {
isSystemVariable = resin.models.environmentVariables.isSystemVariable;
environmentVariables = _.reject(environmentVariables, isSystemVariable);
}
return console.log(visuals.table.horizontal(environmentVariables, ['id', 'name', 'value']));
}).nodeify(done);
}
};
@ -54,38 +58,56 @@
options: [commandOptions.yes, commandOptions.booleanDevice],
permission: 'user',
action: function(params, options, done) {
return visuals.patterns.remove('environment variable', options.yes, function(callback) {
return patterns.confirm(options.yes, 'Are you sure you want to delete the environment variable?').then(function() {
if (options.device) {
return resin.models.environmentVariables.device.remove(params.id, callback);
resin.models.environmentVariables.device.remove(params.id);
return events.send('deviceEnvironmentVariable.delete', {
id: params.id
});
} else {
return resin.models.environmentVariables.remove(params.id, callback);
resin.models.environmentVariables.remove(params.id);
return events.send('environmentVariable.delete', {
id: params.id
});
}
}, done);
}).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\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 MyDevice',
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");
}
}
}
if (options.application != null) {
return resin.models.environmentVariables.create(options.application, params.key, params.value, done);
} else if (options.device != null) {
return resin.models.environmentVariables.device.create(options.device, params.key, params.value, done);
} else {
return done(new Error('You must specify an application or device'));
}
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);
}
};
@ -96,11 +118,21 @@
permission: 'user',
options: [commandOptions.booleanDevice],
action: function(params, options, done) {
if (options.device) {
return resin.models.environmentVariables.device.update(params.id, params.value, done);
} else {
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);
}
};

View File

@ -1,89 +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', 'name', 'display_name', 'repository', 'author']));
}
};
exports.info = {
signature: 'example <name>',
description: 'list a single example application',
help: 'Use this command to show information of a single example application\n\nExample:\n\n $ resin example cimon',
permission: 'user',
action: function(params, options, done) {
var example;
example = _.findWhere(examplesData, {
name: params.name
});
if (example == null) {
return done(new Error("Unknown example: " + params.name));
}
if (example.author == null) {
example.author = 'Unknown';
}
console.log(visuals.widgets.table.vertical(example, ['name', 'display_name', 'description', 'author', 'repository']));
return done();
}
};
exports.clone = {
signature: 'example clone <name>',
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 cimon',
permission: 'user',
action: function(params, options, done) {
var currentDirectory, destination, example;
example = _.findWhere(examplesData, {
name: params.name
});
if (example == null) {
return done(new Error("Unknown example: " + params.name));
}
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);

View File

@ -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);

View File

@ -1,19 +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'),
help: require('./help'),
examples: require('./examples'),
plugin: require('./plugin'),
update: require('./update')
os: require('./os')
};
}).call(this);

View File

@ -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);
}
};

View File

@ -1,23 +1,16 @@
(function() {
var LOGS_HISTORY_COUNT, _, resin;
var _, resin;
_ = require('lodash');
resin = require('resin-sdk');
LOGS_HISTORY_COUNT = 200;
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);
}
};

View File

@ -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);
}
};

177
build/actions/os.js Normal file
View File

@ -0,0 +1,177 @@
(function() {
var Promise, _, commandOptions, form, fs, helpers, init, manager, patterns, resin, rindle, stepHandler, umount, unzip, visuals;
fs = require('fs');
_ = require('lodash');
Promise = require('bluebird');
umount = Promise.promisifyAll(require('umount'));
unzip = require('unzip2');
rindle = require('rindle');
resin = require('resin-sdk');
manager = require('resin-image-manager');
visuals = require('resin-cli-visuals');
form = require('resin-cli-form');
init = require('resin-device-init');
commandOptions = require('./command-options');
helpers = require('../utils/helpers');
patterns = require('../utils/patterns');
exports.download = {
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: [
{
signature: 'output',
description: 'output path',
parameter: 'output',
alias: 'o',
required: 'You have to specify an output location'
}
],
action: function(params, options, done) {
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();
}
});
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);
}
};
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) {
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
});
if (advancedGroup != null) {
override = helpers.getGroupDefaults(advancedGroup);
}
}
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;
}
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);
}
};
}).call(this);

View File

@ -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);

View File

@ -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);

View File

@ -1,23 +0,0 @@
(function() {
var packageJSON, selfupdate;
selfupdate = require('selfupdate');
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 selfupdate.update(packageJSON, function(error, version) {
if (error != null) {
return done(error);
}
console.info("Updated " + packageJSON.name + " to version " + version + ".");
return done();
});
}
};
}).call(this);

43
build/actions/wizard.js Normal file
View 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);

View File

@ -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);
@ -82,34 +56,24 @@
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.await);
capitano.command(actions.device.info);
capitano.command(actions.device.remove);
capitano.command(actions.device.identify);
capitano.command(actions.drive.list);
capitano.command(actions.device.register);
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,52 +90,20 @@
capitano.command(actions.env.remove);
capitano.command(actions.os.download);
capitano.command(actions.os.configure);
capitano.command(actions.os.initialize);
capitano.command(actions.logs);
capitano.command(actions.examples.list);
update.notify();
capitano.command(actions.examples.clone);
capitano.command(actions.examples.info);
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 || !process.stdout.isTTY) {
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);

View File

@ -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."
}
]

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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
View 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
View 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
View 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
View 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
View 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);

View File

@ -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"
]
}
]

View File

@ -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

View File

@ -16,91 +16,69 @@ Now you have access to all the commands referenced below.
- Application
- [app create &#60;name&#62;](#/pages/using/cli.md#app-create-60-name-62-)
- [apps](#/pages/using/cli.md#apps)
- [app &#60;name&#62;](#/pages/using/cli.md#app-60-name-62-)
- [app restart &#60;name&#62;](#/pages/using/cli.md#app-restart-60-name-62-)
- [app rm &#60;name&#62;](#/pages/using/cli.md#app-rm-60-name-62-)
- [app associate &#60;name&#62;](#/pages/using/cli.md#app-associate-60-name-62-)
- [init](#/pages/using/cli.md#init)
- [app create &#60;name&#62;](#app-create-60-name-62-)
- [apps](#apps)
- [app &#60;name&#62;](#app-60-name-62-)
- [app restart &#60;name&#62;](#app-restart-60-name-62-)
- [app rm &#60;name&#62;](#app-rm-60-name-62-)
- [app associate &#60;name&#62;](#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 &#60;name&#62;](#/pages/using/cli.md#device-60-name-62-)
- [device rm &#60;name&#62;](#/pages/using/cli.md#device-rm-60-name-62-)
- [device identify &#60;uuid&#62;](#/pages/using/cli.md#device-identify-60-uuid-62-)
- [device rename &#60;name&#62; [newName]](#/pages/using/cli.md#device-rename-60-name-62-newname-)
- [devices supported](#/pages/using/cli.md#devices-supported)
- [device await &#60;name&#62;](#/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 &#60;uuid&#62;](#device-60-uuid-62-)
- [device register &#60;application&#62;](#device-register-60-application-62-)
- [device rm &#60;uuid&#62;](#device-rm-60-uuid-62-)
- [device identify &#60;uuid&#62;](#device-identify-60-uuid-62-)
- [device rename &#60;uuid&#62; [newName]](#device-rename-60-uuid-62-newname-)
- [device init](#device-init)
- Environment Variables
- [envs](#/pages/using/cli.md#envs)
- [env rm &#60;id&#62;](#/pages/using/cli.md#env-rm-60-id-62-)
- [env add &#60;key&#62; [value]](#/pages/using/cli.md#env-add-60-key-62-value-)
- [env rename &#60;id&#62; &#60;value&#62;](#/pages/using/cli.md#env-rename-60-id-62-60-value-62-)
- Examples
- [examples](#/pages/using/cli.md#examples)
- [example &#60;id&#62;](#/pages/using/cli.md#example-60-id-62-)
- [example clone &#60;id&#62;](#/pages/using/cli.md#example-clone-60-id-62-)
- [envs](#envs)
- [env rm &#60;id&#62;](#env-rm-60-id-62-)
- [env add &#60;key&#62; [value]](#env-add-60-key-62-value-)
- [env rename &#60;id&#62; &#60;value&#62;](#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 &#60;id&#62;](#/pages/using/cli.md#key-60-id-62-)
- [key rm &#60;id&#62;](#/pages/using/cli.md#key-rm-60-id-62-)
- [key add &#60;name&#62; [path]](#/pages/using/cli.md#key-add-60-name-62-path-)
- [keys](#keys)
- [key &#60;id&#62;](#key-60-id-62-)
- [key rm &#60;id&#62;](#key-rm-60-id-62-)
- [key add &#60;name&#62; [path]](#key-add-60-name-62-path-)
- Logs
- [logs &#60;uuid&#62;](#/pages/using/cli.md#logs-60-uuid-62-)
- [logs &#60;uuid&#62;](#logs-60-uuid-62-)
- Notes
- [note &#60;|note&#62;](#/pages/using/cli.md#note-60-note-62-)
- [note &#60;|note&#62;](#note-60-note-62-)
- OS
- [os download &#60;name&#62;](#/pages/using/cli.md#os-download-60-name-62-)
- [os install &#60;image&#62; [device]](#/pages/using/cli.md#os-install-60-image-62-device-)
- [os download &#60;type&#62;](#os-download-60-type-62-)
- [os configure &#60;image&#62; &#60;uuid&#62;](#os-configure-60-image-62-60-uuid-62-)
- [os initialize &#60;image&#62;](#os-initialize-60-image-62-)
- Plugin
- Wizard
- [plugins](#/pages/using/cli.md#plugins)
- [plugin install &#60;name&#62;](#/pages/using/cli.md#plugin-install-60-name-62-)
- [plugin update &#60;name&#62;](#/pages/using/cli.md#plugin-update-60-name-62-)
- [plugin rm &#60;name&#62;](#/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
@ -183,21 +161,12 @@ 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
@ -205,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:
@ -235,28 +202,12 @@ Examples:
Username: johndoe
Password: ***********
$ resin signup --email me@mycompany.com --username johndoe --password ***********
$ resin whoami
johndoe
### Options
#### --email, -e &#60;email&#62;
user email
#### --username, -u &#60;username&#62;
user name
#### --password, -p &#60;user password&#62;
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:
@ -283,15 +234,29 @@ Examples:
application name
## device &#60;name&#62;
## device &#60;uuid&#62;
Use this command to show information about a single device.
Examples:
$ resin device MyDevice
$ resin device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
## device rm &#60;name&#62;
## device register &#60;application&#62;
Use this command to register a device to an application.
Examples:
$ resin device register MyApp
### Options
#### --uuid, -u &#60;uuid&#62;
custom uuid
## device rm &#60;uuid&#62;
Use this command to remove a device from resin.io.
@ -300,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
@ -319,7 +284,7 @@ Examples:
$ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
## device rename &#60;name&#62; [newName]
## device rename &#60;uuid&#62; [newName]
Use this command to rename a device.
@ -327,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 &#60;name&#62;
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 &#60;interval&#62;
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
@ -398,43 +313,30 @@ Examples:
application name
#### --network, -n &#60;network&#62;
#### --yes, -y
network type
confirm non interactively
#### --ssid, -s &#60;ssid&#62;
#### --advanced, -v
wifi ssid, if network is wifi
#### --key, -k &#60;key&#62;
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
@ -442,6 +344,10 @@ Example:
application name
#### --device, -d &#60;device&#62;
device name
#### --verbose, -v
show private environment variables
@ -455,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
@ -466,22 +375,28 @@ Examples:
confirm non interactively
#### --device, -d
device name
## env add &#60;key&#62; [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
@ -489,42 +404,26 @@ Examples:
application name
#### --device, -d &#60;device&#62;
device name
## env rename &#60;id&#62; &#60;value&#62;
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 &#60;id&#62;
Use this command to show information of a single example application
Example:
$ resin example 3
## example clone &#60;id&#62;
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
@ -537,6 +436,12 @@ Examples:
$ resin help apps
$ resin help os download
### Options
#### --verbose, -v
show additional commands
# Information
## version
@ -599,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.
@ -611,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 &#60;num&#62;
number of lines to display
#### --tail, -t
continuously stream output
@ -632,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 &#60;device&#62;
device name
device uuid
# OS
## os download &#60;name&#62;
## os download &#60;type&#62;
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 &#60;network&#62;
network type
#### --ssid, -s &#60;ssid&#62;
wifi ssid, if network is wifi
#### --key, -k &#60;key&#62;
wifi key, if network is wifi
#### --output, -o &#60;output&#62;
output file
output path
## os install &#60;image&#62; [device]
## os configure &#60;image&#62; &#60;uuid&#62;
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 &#60;image&#62;
Use this command to initialize a previously configured operating system image.
Examples:
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
### Options
@ -710,82 +586,29 @@ Examples:
confirm non interactively
# Plugin
#### --type, -t &#60;type&#62;
## plugins
device type
Use this command to list all the installed resin plugins.
#### --drive, -d &#60;drive&#62;
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 &#60;name&#62;
Use this command to install a resin plugin
Use `--quiet` to prevent information logging.
Examples:
$ resin plugin install hello
## plugin update &#60;name&#62;
Use this command to update a resin plugin
Use `--quiet` to prevent information logging.
Examples:
$ resin plugin update hello
## plugin rm &#60;name&#62;
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

View File

@ -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-')

View File

@ -1,9 +1,7 @@
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')
packageJSON = require('./package.json')
@ -15,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: '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({
@ -50,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' ])

View File

@ -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,31 +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) ->
options.type = type
resin.models.application.create(params.name, options.type, callback)
(applicationId, callback) ->
console.info("Application created: #{params.name} (#{options.type}, id #{applicationId})")
return 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'
@ -73,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>'
@ -96,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>'
@ -120,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>'
@ -139,94 +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.
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
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
currentDirectory = process.cwd()
async.waterfall [
(callback) ->
resin.models.application.has(params.name, callback)
(hasApp, callback) ->
if not hasApp
return callback(new Error("Invalid application: #{params.name}"))
message = "Are you sure you want to associate #{currentDirectory} with #{params.name}?"
visuals.patterns.confirm(options.yes, message, callback)
(confirmed, callback) ->
return done() if not confirmed
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)

View File

@ -1,62 +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('dashboardUrl'), '/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) ->
async.waterfall([
(callback) ->
return callback(null, params.token) if params.token?
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...
"""
open TOKEN_URL, (error) ->
if error?
console.error """
Unable to open a web browser in the current environment.
Please visit #{TOKEN_URL} manually.
"""
visuals.widgets.ask('What\'s your token? (visible in the preferences page)', null, callback)
(token, callback) ->
resin.auth.loginWithToken(token, callback)
(callback) ->
resin.auth.whoami(callback)
(username, callback) ->
console.info("Successfully logged in as: #{username}")
return callback()
], 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'
@ -70,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'
@ -87,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:
@ -154,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)

View File

@ -1,19 +1,14 @@
fse = require('fs-extra')
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')
manager = require('resin-image-manager')
image = require('resin-image')
inject = require('resin-config-inject')
registerDevice = require('resin-register-device')
pine = require('resin-pine')
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')
@ -35,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'
@ -53,26 +47,31 @@ exports.list =
'status'
'last_seen'
]
return done(null, devices)
.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'
@ -85,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.
@ -99,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>'
@ -123,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.
@ -135,228 +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 and other logging information by passing the `--quiet` boolean option.
You need to configure the network type and other settings:
Ethernet:
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
Wifi:
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
You can omit network related options to be asked about them interactively.
Examples:
$ 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'
root: true
primary: true
action: (params, options, done) ->
Promise.try ->
return options.application if options.application?
return patterns.selectApplication()
.then(resin.models.application.get)
.then (application) ->
networkOptions =
network: options.network
wifiSsid: options.ssid
wifiKey: options.key
download = ->
tmp.tmpNameAsync().then (temporalPath) ->
capitano.runAsync("os download #{application.device_type} --output #{temporalPath}")
.disposer (temporalPath) ->
return rimraf(temporalPath)
async.waterfall([
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 ->
(callback) ->
return callback(null, options.application) if options.application?
vcs.getApplicationName(process.cwd(), callback)
helpers.sudo([ 'os', 'initialize', temporalPath, '--type', application.device_type ])
.then (device) ->
console.log('Done')
return device.uuid
(applicationName, callback) ->
options.application = applicationName
resin.models.application.has(options.application, callback)
(hasApplication, callback) ->
if not hasApplication
return callback(new Error("Invalid application: #{options.application}"))
return callback(null, params.device) if params.device?
visuals.patterns.selectDrive(callback)
(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
return callback() if networkOptions.network?
visuals.patterns.selectNetworkParameters (error, parameters) ->
return callback(error) if error?
_.extend(networkOptions, parameters)
return callback()
(callback) ->
console.info("Checking application: #{options.application}")
resin.models.application.get(options.application, callback)
(application, callback) ->
async.parallel
manifest: (callback) ->
console.info('Getting device manifest for the application')
resin.models.device.getManifestBySlug(application.device_type, callback)
config: (callback) ->
console.info('Fetching application configuration')
resin.models.application.getConfiguration(options.application, networkOptions, callback)
, callback
(results, callback) ->
console.info('Associating the device')
registerDevice.register pine, results.config, (error, device) ->
return callback(error) if error?
# Associate a device
results.config.deviceId = device.id
results.config.uuid = device.uuid
params.uuid = results.config.uuid
return callback(null, results)
(results, callback) ->
console.info('Configuring device operating system image')
if process.env.DEBUG
console.log(results.config)
bar = new visuals.widgets.Progress('Downloading Device OS')
spinner = new visuals.widgets.Spinner('Downloading Device OS (size unknown)')
manager.configure results.manifest, results.config, (error, imagePath, removeCallback) ->
spinner.stop()
return callback(error, imagePath, removeCallback)
, (state) ->
if state?
bar.update(state)
else
spinner.start()
(configuredImagePath, removeCallback, callback) ->
console.info('Attempting to write operating system image to drive')
bar = new visuals.widgets.Progress('Writing Device OS')
image.write
device: params.device
image: configuredImagePath
progress: _.bind(bar.update, bar)
, (error) ->
return callback(error) if error?
return callback(null, configuredImagePath, removeCallback)
(temporalImagePath, removeCallback, callback) ->
console.info('Image written successfully')
removeCallback(callback)
(callback) ->
resin.models.device.getByUUID(params.uuid, callback)
(device, callback) ->
console.info("Device created: #{device.name}")
return callback(null, device.name)
], done)
.nodeify(done)

View File

@ -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()

View File

@ -1,8 +1,10 @@
async = require('async')
_ = 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'
@ -19,7 +21,7 @@ exports.list =
$ resin envs --application MyApp
$ resin envs --application MyApp --verbose
$ resin envs --device MyDevice
$ resin envs --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
'''
options: [
commandOptions.optionalApplication
@ -34,31 +36,27 @@ exports.list =
]
permission: 'user'
action: (params, options, done) ->
async.waterfall([
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')
(callback) ->
if options.application?
resin.models.environmentVariables.getAllByApplication(options.application, callback)
else if options.device?
resin.models.environmentVariables.device.getAll(options.device, callback)
else
return callback(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
isSystemVariable = resin.models.environmentVariables.isSystemVariable
environmentVariables = _.reject(environmentVariables, isSystemVariable)
(environmentVariables, callback) ->
if not options.verbose
isSystemVariable = resin.models.environmentVariables.isSystemVariable
environmentVariables = _.reject(environmentVariables, isSystemVariable)
console.log visuals.widgets.table.horizontal environmentVariables, [
'id'
'name'
'value'
]
return callback()
], done)
console.log visuals.table.horizontal environmentVariables, [
'id'
'name'
'value'
]
.nodeify(done)
exports.remove =
signature: 'env rm <id>'
@ -85,12 +83,14 @@ exports.remove =
]
permission: 'user'
action: (params, options, done) ->
visuals.patterns.remove 'environment variable', options.yes, (callback) ->
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, callback)
resin.models.environmentVariables.device.remove(params.id)
events.send('deviceEnvironmentVariable.delete', id: params.id)
else
resin.models.environmentVariables.remove(params.id, callback)
, done
resin.models.environmentVariables.remove(params.id)
events.send('environmentVariable.delete', id: params.id)
.nodeify(done)
exports.add =
signature: 'env add <key> [value]'
@ -111,7 +111,7 @@ exports.add =
$ resin env add EDITOR vim --application MyApp
$ resin env add TERM --application MyApp
$ resin env add EDITOR vim --device MyDevice
$ resin env add EDITOR vim --device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
'''
options: [
commandOptions.optionalApplication
@ -119,20 +119,25 @@ exports.add =
]
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]
if options.application?
resin.models.environmentVariables.create(options.application, params.key, params.value, done)
else if options.device?
resin.models.environmentVariables.device.create(options.device, params.key, params.value, done)
else
return done(new Error('You must specify an application or device'))
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>'
@ -150,7 +155,11 @@ exports.rename =
permission: 'user'
options: [ commandOptions.booleanDevice ]
action: (params, options, done) ->
if options.device
resin.models.environmentVariables.device.update(params.id, params.value, done)
else
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)

View File

@ -1,95 +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'
'name'
'display_name'
'repository'
'author'
]
exports.info =
signature: 'example <name>'
description: 'list a single example application'
help: '''
Use this command to show information of a single example application
Example:
$ resin example cimon
'''
permission: 'user'
action: (params, options, done) ->
example = _.findWhere(examplesData, name: params.name)
if not example?
return done(new Error("Unknown example: #{params.name}"))
example.author ?= 'Unknown'
console.log visuals.widgets.table.vertical example, [
'name'
'display_name'
'description'
'author'
'repository'
]
return done()
exports.clone =
signature: 'example clone <name>'
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 cimon
'''
permission: 'user'
action: (params, options, done) ->
example = _.findWhere(examplesData, name: params.name)
if not example?
return done(new Error("Unknown example: #{params.name}"))
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()

View File

@ -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)

View File

@ -1,15 +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')
help: require('./help')
examples: require('./examples')
plugin: require('./plugin')
update: require('./update')
os: require('./os')

View File

@ -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)

View File

@ -1,8 +1,6 @@
_ = require('lodash')
resin = require('resin-sdk')
LOGS_HISTORY_COUNT = 200
module.exports =
signature: 'logs <uuid>'
description: 'show device logs'
@ -11,9 +9,6 @@ module.exports =
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 @@ module.exports =
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 @@ module.exports =
}
]
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)

View File

@ -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)

164
lib/actions/os.coffee Normal file
View File

@ -0,0 +1,164 @@
fs = require('fs')
_ = require('lodash')
Promise = require('bluebird')
umount = Promise.promisifyAll(require('umount'))
unzip = require('unzip2')
rindle = require('rindle')
resin = require('resin-sdk')
manager = require('resin-image-manager')
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
init = require('resin-device-init')
commandOptions = require('./command-options')
helpers = require('../utils/helpers')
patterns = require('../utils/patterns')
exports.download =
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.
Examples:
$ resin os download parallella -o ../foo/bar/parallella.img
'''
permission: 'user'
options: [
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: '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: (params, options, done) ->
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)

View File

@ -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()

View File

@ -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)

View File

@ -1,26 +0,0 @@
selfupdate = require('selfupdate')
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) ->
selfupdate.update packageJSON, (error, version) ->
return done(error) if error?
console.info("Updated #{packageJSON.name} to version #{version}.")
return done()

42
lib/actions/wizard.coffee Normal file
View 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)

View File

@ -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)
@ -62,29 +38,20 @@ capitano.command(actions.app.create)
capitano.command(actions.app.list)
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.await)
capitano.command(actions.device.info)
capitano.command(actions.device.remove)
capitano.command(actions.device.identify)
# ---------- 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,49 +64,17 @@ capitano.command(actions.env.add)
capitano.command(actions.env.rename)
capitano.command(actions.env.remove)
# ---------- OS Module ----------
capitano.command(actions.os.download)
capitano.command(actions.os.configure)
capitano.command(actions.os.initialize)
# ---------- Logs Module ----------
capitano.command(actions.logs)
# ---------- Examples Module ----------
capitano.command(actions.examples.list)
capitano.command(actions.examples.clone)
capitano.command(actions.examples.info)
update.notify()
# ---------- Plugins Module ----------
capitano.command(actions.plugin.list)
capitano.command(actions.plugin.install)
capitano.command(actions.plugin.update)
capitano.command(actions.plugin.remove)
# ---------- 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 or not process.stdout.isTTY
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)

View File

@ -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."
}
]

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
View 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
View 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
View 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
View 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')

View 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

View File

@ -1,48 +0,0 @@
.TH "RESIN" "1" "May 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

View File

@ -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>

View File

@ -1,196 +0,0 @@
.TH "RESIN\-PLUGINS" "1" "May 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\.

View File

@ -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.

View File

@ -1,12 +0,0 @@
.TH "RESIN" "1" "May 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

View File

@ -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>

View File

@ -1,6 +1,6 @@
{
"name": "resin-cli",
"version": "0.11.0",
"version": "2.0.1",
"description": "Git Push to your devices",
"main": "./build/actions/index.js",
"homepage": "https://github.com/resin-io/resin-cli",
@ -9,11 +9,6 @@
"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,51 +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",
"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.6.0",
"coffee-script": "~1.8.0",
"conf.js": "^0.1.1",
"drivelist": "^1.2.2",
"fs-extra": "^0.18.3",
"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",
"resin-cli-visuals": "^0.1.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-image": "^1.1.3",
"resin-image-manager": "^1.1.0",
"resin-pine": "^1.1.1",
"resin-register-device": "^1.0.1",
"resin-sdk": "^1.7.4",
"resin-settings-client": "^1.1.0",
"resin-vcs": "^1.2.0",
"selfupdate": "^1.1.0",
"tmp": "^0.0.25",
"underscore.string": "~2.4.0",
"update-notifier": "^0.3.1"
"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"
}
}

View File

@ -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./)