diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e70634d..8bd0fb7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! This project adheres to [Semantic Versioning](http://semver.org/). +## v6.12.3 - 2018-01-09 + +* Lint TypeScript and CoffeeScript with resin-lint #743 [Tim Perry] +* Move documentation generation to TypeScript #743 [Tim Perry] +* Convert most of utils to TypeScript #743 [Tim Perry] + ## v6.12.2 - 2018-01-09 * Convert windows paths to posix when passing to tar #748 [Andrew Shirley] diff --git a/automation/custom-types.d.ts b/automation/custom-types.d.ts index cf0761d6..03240a27 100644 --- a/automation/custom-types.d.ts +++ b/automation/custom-types.d.ts @@ -16,13 +16,13 @@ declare module 'filehound' { declare module 'publish-release' { interface PublishOptions { - token: string, - owner: string, - repo: string, - tag: string, - name: string, - reuseRelease?: boolean - assets: string[] + token: string; + owner: string; + repo: string; + tag: string; + name: string; + reuseRelease?: boolean; + assets: string[]; } interface Release { diff --git a/automation/deploy-bin.ts b/automation/deploy-bin.ts index 390d06ce..5a5e192c 100644 --- a/automation/deploy-bin.ts +++ b/automation/deploy-bin.ts @@ -5,6 +5,7 @@ import * as fs from 'fs-extra'; import * as mkdirp from 'mkdirp'; import * as publishRelease from 'publish-release'; import * as archiver from 'archiver'; +import * as packageJSON from '../package.json'; const publishReleaseAsync = Promise.promisify(publishRelease); const mkdirpAsync = Promise.promisify(mkdirp); @@ -12,14 +13,14 @@ const mkdirpAsync = Promise.promisify(mkdirp); const { GITHUB_TOKEN } = process.env; const ROOT = path.join(__dirname, '..'); -const version = 'v' + require('../package.json').version; +const version = 'v' + packageJSON.version; const outputFile = path.join(ROOT, 'build-zip', `resin-cli-${version}-${os.platform()}-${os.arch()}.zip`); mkdirpAsync(path.dirname(outputFile)).then(() => new Promise((resolve, reject) => { console.log('Zipping build...'); let archive = archiver('zip', { - zlib: { level: 7 } + zlib: { level: 7 }, }); archive.directory(path.join(ROOT, 'build-bin'), 'resin-cli'); @@ -44,7 +45,7 @@ mkdirpAsync(path.dirname(outputFile)).then(() => new Promise((resolve, reject) = tag: version, name: `Resin-CLI ${version}`, reuseRelease: true, - assets: [outputFile] + assets: [outputFile], }); }).then((release) => { console.log(`Release ${version} successful: ${release.html_url}`); diff --git a/automation/tsconfig.json b/automation/tsconfig.json index 0462794a..938fd3d7 100644 --- a/automation/tsconfig.json +++ b/automation/tsconfig.json @@ -10,6 +10,7 @@ "sourceMap": true }, "include": [ - "./**/*.ts" + "./**/*.ts", + "../typings/*.d.ts" ] } diff --git a/capitanodoc.ts b/capitanodoc.ts new file mode 100644 index 00000000..9cda44b9 --- /dev/null +++ b/capitanodoc.ts @@ -0,0 +1,114 @@ +export = { + title: 'Resin CLI Documentation', + introduction: `\ +This tool allows you to interact with the resin.io api from the comfort of your command line. + +Please make sure your system meets the requirements as specified in the [README](https://github.com/resin-io/resin-cli). + +To get started download the CLI from npm. + + $ npm install resin-cli -g + +Then authenticate yourself: + + $ resin login + +Now you have access to all the commands referenced below. + +## Proxy support + +The CLI does support HTTP(S) proxies. + +You can configure the proxy using several methods (in order of their precedence): + +* set the \`RESINRC_PROXY\` environment variable in the URL format (with protocol, host, port, and optionally the basic auth), +* use the [resin config file](https://www.npmjs.com/package/resin-settings-client#documentation) (project-specific or user-level) +and set the \`proxy\` setting. This can be: + * a string in the URL format, + * or an object following [this format](https://www.npmjs.com/package/global-tunnel-ng#options), which allows more control, +* or set the conventional \`https_proxy\` / \`HTTPS_PROXY\` / \`http_proxy\` / \`HTTP_PROXY\` +environment variable (in the same standard URL format).\ +`, + + categories: [ + { + title: 'Application', + files: [ 'build/actions/app.js' ] + }, + { + title: 'Authentication', + files: [ 'build/actions/auth.js' ] + }, + { + title: 'Device', + files: [ 'build/actions/device.js' ] + }, + { + title: 'Environment Variables', + files: [ 'build/actions/environment-variables.js' ] + }, + { + title: 'Help', + files: [ 'build/actions/help.js' ] + }, + { + title: 'Information', + files: [ 'build/actions/info.js' ] + }, + { + title: 'Keys', + files: [ 'build/actions/keys.js' ] + }, + { + title: 'Logs', + files: [ 'build/actions/logs.js' ] + }, + { + title: 'Sync', + files: [ 'build/actions/sync.js' ] + }, + { + title: 'SSH', + files: [ 'build/actions/ssh.js' ] + }, + { + title: 'Notes', + files: [ 'build/actions/notes.js' ] + }, + { + title: 'OS', + files: [ 'build/actions/os.js' ] + }, + { + title: 'Config', + files: [ 'build/actions/config.js' ] + }, + { + title: 'Preload', + files: [ 'build/actions/preload.js' ] + }, + { + title: 'Settings', + files: [ 'build/actions/settings.js' ] + }, + { + title: 'Wizard', + files: [ 'build/actions/wizard.js' ] + }, + { + title: 'Local', + files: [ 'build/actions/local/index.js' ] + }, + { + title: 'Deploy', + files: [ + 'build/actions/build.js', + 'build/actions/deploy.js' + ] + }, + { + title: 'Utilities', + files: [ 'build/actions/util.js' ] + }, + ] +}; diff --git a/doc/cli.markdown b/doc/cli.markdown index 3cb7a2cc..9caea2f0 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -254,7 +254,7 @@ web-based login credential-based login -#### --email, --e,u, --e,u <email> +#### --email, -e, -u <email> email @@ -310,7 +310,7 @@ Examples: ### Options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name @@ -464,7 +464,7 @@ Examples: ### Options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name @@ -482,7 +482,7 @@ Examples: ### Options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name @@ -529,7 +529,7 @@ Example: ### Options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name @@ -589,7 +589,7 @@ Examples: ### Options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name @@ -845,7 +845,7 @@ Examples: ### Options -#### --device, --d,dev, --d,dev <device> +#### --device, -d, --dev <device> device uuid @@ -937,7 +937,7 @@ Examples: show advanced configuration options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name @@ -1081,7 +1081,7 @@ Examples: ### Options -#### --application, --a,app, --a,app <application> +#### --application, -a, --app <application> application name diff --git a/extras/capitanodoc/doc-types.d.ts b/extras/capitanodoc/doc-types.d.ts new file mode 100644 index 00000000..536cf6c8 --- /dev/null +++ b/extras/capitanodoc/doc-types.d.ts @@ -0,0 +1,14 @@ +import { CommandDefinition } from 'capitano'; + +export interface Document { + title: string; + introduction: string; + categories: Category[]; +} + +export interface Category { + title: string; + commands: CommandDefinition[]; +} + +export { CommandDefinition as Command }; diff --git a/extras/capitanodoc/index.coffee b/extras/capitanodoc/index.coffee deleted file mode 100644 index c2f2223b..00000000 --- a/extras/capitanodoc/index.coffee +++ /dev/null @@ -1,46 +0,0 @@ -_ = require('lodash') -path = require('path') -capitanodoc = require('../../capitanodoc') -markdown = require('./markdown') - -result = {} -result.title = capitanodoc.title -result.introduction = capitanodoc.introduction -result.categories = [] - -for commandCategory in capitanodoc.categories - category = {} - category.title = commandCategory.title - category.commands = [] - - for file in commandCategory.files - actions = require(path.join(process.cwd(), file)) - - 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) - -result.toc = _.cloneDeep(result.categories) -result.toc = _.map result.toc, (category) -> - category.commands = _.map category.commands, (command) -> - return { - signature: command.signature - anchor: '#' + command.signature - .replace(/\s/g,'-') - .replace(//g, '-62-') - .replace(/\[/g, '') - .replace(/\]/g, '-') - .replace(/--/g, '-') - .replace(/\.\.\./g, '') - .replace(/\|/g, '') - .toLowerCase() - } - - return category - -console.log(markdown.display(result)) diff --git a/extras/capitanodoc/index.ts b/extras/capitanodoc/index.ts new file mode 100644 index 00000000..875ee344 --- /dev/null +++ b/extras/capitanodoc/index.ts @@ -0,0 +1,34 @@ +import capitanodoc = require('../../capitanodoc'); +import * as _ from 'lodash'; +import * as path from 'path'; +import * as markdown from './markdown'; +import { Document, Category } from './doc-types'; + +const result = {}; +result.title = capitanodoc.title; +result.introduction = capitanodoc.introduction; +result.categories = []; + +for (let commandCategory of capitanodoc.categories) { + const category = {}; + category.title = commandCategory.title; + category.commands = []; + + for (let file of commandCategory.files) { + // tslint:disable-next-line:no-var-requires + const actions: any = require(path.join(process.cwd(), file)); + + if (actions.signature) { + category.commands.push(_.omit(actions, 'action')); + } else { + for (let actionName of Object.keys(actions)) { + const actionCommand = actions[actionName]; + category.commands.push(_.omit(actionCommand, 'action')); + } + } + } + + result.categories.push(category); +} + +console.log(markdown.render(result)); diff --git a/extras/capitanodoc/markdown.coffee b/extras/capitanodoc/markdown.coffee deleted file mode 100644 index 85c0d6e1..00000000 --- a/extras/capitanodoc/markdown.coffee +++ /dev/null @@ -1,66 +0,0 @@ -_ = require('lodash') -ent = require('ent') -utils = require('./utils') - -exports.command = (command) -> - result = """ - ## #{ent.encode(command.signature)} - - #{command.help}\n - """ - - if not _.isEmpty(command.options) - result += '\n### Options' - - for option in command.options - result += """ - \n\n#### #{utils.parseSignature(option)} - - #{option.description} - """ - - result += '\n' - - return result - -exports.category = (category) -> - result = """ - # #{category.title}\n - """ - - for command in category.commands - result += '\n' + exports.command(command) - - return result - -exports.toc = (toc) -> - result = ''' - # Table of contents\n - ''' - - for category in toc - - result += """ - \n- #{category.title}\n\n - """ - - for command in category.commands - result += """ - \t- [#{ent.encode(command.signature)}](#{command.anchor})\n - """ - - return result - -exports.display = (doc) -> - result = """ - # #{doc.title} - - #{doc.introduction} - - #{exports.toc(doc.toc)} - """ - - for category in doc.categories - result += '\n' + exports.category(category) - - return result diff --git a/extras/capitanodoc/markdown.ts b/extras/capitanodoc/markdown.ts new file mode 100644 index 00000000..2a2d7510 --- /dev/null +++ b/extras/capitanodoc/markdown.ts @@ -0,0 +1,68 @@ +import * as _ from 'lodash'; +import * as ent from 'ent'; +import * as utils from './utils'; +import { Document, Category, Command } from './doc-types'; + +export function renderCommand(command: Command) { + let result = `## ${ent.encode(command.signature)}\n\n${command.help}\n`; + + if (!_.isEmpty(command.options)) { + result += '\n### Options'; + + for (let option of command.options!) { + result += `\n\n#### ${utils.parseSignature(option)}\n\n${option.description}`; + } + + result += '\n'; + } + + return result; +} + +export function renderCategory(category: Category) { + let result = `# ${category.title}\n`; + + for (let command of category.commands) { + result += `\n${renderCommand(command)}`; + } + + return result; +} + +function getAnchor(command: Command) { + return '#' + command.signature + .replace(/\s/g,'-') + .replace(//g, '-62-') + .replace(/\[/g, '') + .replace(/\]/g, '-') + .replace(/--/g, '-') + .replace(/\.\.\./g, '') + .replace(/\|/g, '') + .toLowerCase(); +} + +export function renderToc(categories: Category[]) { + let result = `# Table of contents\n`; + + for (let category of categories) { + + result += `\n- ${category.title}\n\n`; + + for (let command of category.commands) { + result += `\t- [${ent.encode(command.signature)}](${getAnchor(command)})\n`; + } + } + + return result; +} + +export function render(doc: Document) { + let result = `# ${doc.title}\n\n${doc.introduction}\n\n${renderToc(doc.categories)}`; + + for (let category of doc.categories) { + result += `\n${renderCategory(category)}`; + } + + return result; +} diff --git a/extras/capitanodoc/utils.coffee b/extras/capitanodoc/utils.coffee deleted file mode 100644 index 84eac0dc..00000000 --- a/extras/capitanodoc/utils.coffee +++ /dev/null @@ -1,26 +0,0 @@ -_ = require('lodash') -ent = require('ent') - -exports.getOptionPrefix = (signature) -> - if signature.length > 1 - return '--' - else - return '-' - -exports.getOptionSignature = (signature) -> - return "#{exports.getOptionPrefix(signature)}#{signature}" - -exports.parseSignature = (option) -> - result = exports.getOptionSignature(option.signature) - - if not _.isEmpty(option.alias) - if _.isString(option.alias) - result += ", #{exports.getOptionSignature(option.alias)}" - else - for alias in option.alias - result += ", #{exports.getOptionSignature(option.alias)}" - - if option.parameter? - result += " <#{option.parameter}>" - - return ent.encode(result) diff --git a/extras/capitanodoc/utils.ts b/extras/capitanodoc/utils.ts new file mode 100644 index 00000000..53725f9c --- /dev/null +++ b/extras/capitanodoc/utils.ts @@ -0,0 +1,33 @@ +import { OptionDefinition } from 'capitano'; +import * as _ from 'lodash'; +import * as ent from 'ent'; + +export function getOptionPrefix(signature: string) { + if (signature.length > 1) { + return '--'; + } else { + return '-'; + } +} + +export function getOptionSignature(signature: string) { + return `${getOptionPrefix(signature)}${signature}`; +} + +export function parseSignature(option: OptionDefinition) { + let result = getOptionSignature(option.signature); + + if (_.isArray(option.alias)) { + for (let alias of option.alias) { + result += `, ${getOptionSignature(alias)}`; + } + } else if (_.isString(option.alias)) { + result += `, ${getOptionSignature(option.alias)}`; + } + + if (option.parameter) { + result += ` <${option.parameter}>`; + } + + return ent.encode(result); +} diff --git a/extras/tsconfig.json b/extras/tsconfig.json new file mode 100644 index 00000000..2fbc2f6e --- /dev/null +++ b/extras/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "outDir": "build", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "preserveConstEnums": true, + "removeComments": true, + "sourceMap": true + }, + "include": [ + "../typings/*.d.ts", + "./**/*.ts" + ] +} diff --git a/gulpfile.coffee b/gulpfile.coffee index 1ce0feb0..3049237b 100644 --- a/gulpfile.coffee +++ b/gulpfile.coffee @@ -1,15 +1,12 @@ path = require('path') gulp = require('gulp') coffee = require('gulp-coffee') -coffeelint = require('gulp-coffeelint') inlinesource = require('gulp-inline-source') mocha = require('gulp-mocha') shell = require('gulp-shell') packageJSON = require('./package.json') OPTIONS = - config: - coffeelint: path.join(__dirname, 'coffeelint.json') files: coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ] app: 'lib/**/*.coffee' @@ -23,18 +20,11 @@ gulp.task 'pages', -> .pipe(inlinesource()) .pipe(gulp.dest('build/auth/pages')) -gulp.task 'coffee', [ 'lint' ], -> +gulp.task 'coffee', -> gulp.src(OPTIONS.files.app) .pipe(coffee(bare: true, header: true)) .pipe(gulp.dest(OPTIONS.directories.build)) -gulp.task 'lint', -> - gulp.src(OPTIONS.files.coffee) - .pipe(coffeelint({ - optFile: OPTIONS.config.coffeelint - })) - .pipe(coffeelint.reporter()) - gulp.task 'test', -> gulp.src(OPTIONS.files.tests, read: false) .pipe(mocha({ diff --git a/lib/actions/build.coffee b/lib/actions/build.coffee index 4bc6bba6..1b55ccfe 100644 --- a/lib/actions/build.coffee +++ b/lib/actions/build.coffee @@ -8,7 +8,7 @@ getBundleInfo = Promise.method (options) -> if options.application? # An application was provided - return helpers.getAppInfo(options.application) + return helpers.getArchAndDeviceType(options.application) .then (app) -> return [app.arch, app.device_type] else if options.arch? and options.deviceType? diff --git a/lib/actions/config.coffee b/lib/actions/config.coffee index eee21f03..61466012 100644 --- a/lib/actions/config.coffee +++ b/lib/actions/config.coffee @@ -271,7 +271,6 @@ exports.generate = Promise = require('bluebird') writeFileAsync = Promise.promisify(require('fs').writeFile) resin = require('resin-sdk-preconfigured') - _ = require('lodash') form = require('resin-cli-form') deviceConfig = require('resin-device-config') prettyjson = require('prettyjson') diff --git a/lib/actions/deploy.coffee b/lib/actions/deploy.coffee index a0ddbb1a..dfb6f430 100644 --- a/lib/actions/deploy.coffee +++ b/lib/actions/deploy.coffee @@ -40,7 +40,7 @@ showPushProgress = (message) -> getBundleInfo = (options) -> helpers = require('../utils/helpers') - helpers.getAppInfo(options.appName) + helpers.getArchAndDeviceType(options.appName) .then (app) -> [app.arch, app.device_type] diff --git a/lib/actions/device.coffee b/lib/actions/device.coffee index c23888b0..db01db95 100644 --- a/lib/actions/device.coffee +++ b/lib/actions/device.coffee @@ -401,7 +401,6 @@ exports.init = tmp.setGracefulCleanup() resin = require('resin-sdk-preconfigured') - helpers = require('../utils/helpers') patterns = require('../utils/patterns') Promise.try -> diff --git a/lib/actions/info.coffee b/lib/actions/info.ts similarity index 60% rename from lib/actions/info.coffee rename to lib/actions/info.ts index 004881fd..52072191 100644 --- a/lib/actions/info.coffee +++ b/lib/actions/info.ts @@ -1,4 +1,4 @@ -### +/* Copyright 2016-2017 Resin.io Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,15 +12,19 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -### +*/ -exports.version = - signature: 'version' - description: 'output the version number' - help: ''' - Display the Resin CLI version. - ''' - action: (params, options, done) -> - packageJSON = require('../../package.json') - console.log(packageJSON.version) - return done() +import { CommandDefinition } from 'capitano'; + +export const version: CommandDefinition = { + signature: 'version', + description: 'output the version number', + help: `\ +Display the Resin CLI version.\ +`, + async action(_params, _options, done) { + const packageJSON = await import('../../package.json'); + console.log(packageJSON.version); + return done(); + }, +}; diff --git a/lib/actions/logs.coffee b/lib/actions/logs.coffee index 4ca186be..0a294acf 100644 --- a/lib/actions/logs.coffee +++ b/lib/actions/logs.coffee @@ -44,7 +44,6 @@ module.exports = permission: 'user' primary: true action: (params, options, done) -> - _ = require('lodash') resin = require('resin-sdk-preconfigured') moment = require('moment') diff --git a/lib/actions/preload.coffee b/lib/actions/preload.coffee index 17ed427f..6cb788c3 100644 --- a/lib/actions/preload.coffee +++ b/lib/actions/preload.coffee @@ -63,7 +63,7 @@ selectApplicationCommit = (builds) -> if builds.length == 0 expectedError('This application has no successful builds.') - DEFAULT_CHOICE = {'name': LATEST, 'value': LATEST} + DEFAULT_CHOICE = { 'name': LATEST, 'value': LATEST } choices = [ DEFAULT_CHOICE ].concat builds.map (build) -> name: "#{build.push_timestamp} - #{build.commit_hash}" value: build.commit_hash @@ -150,8 +150,6 @@ module.exports = _ = require('lodash') Promise = require('bluebird') resin = require('resin-sdk-preconfigured') - streamToPromise = require('stream-to-promise') - form = require('resin-cli-form') preload = require('resin-preload') errors = require('resin-errors') visuals = require('resin-cli-visuals') diff --git a/lib/actions/settings.coffee b/lib/actions/settings.ts similarity index 56% rename from lib/actions/settings.coffee rename to lib/actions/settings.ts index c5be7025..ae76c00d 100644 --- a/lib/actions/settings.coffee +++ b/lib/actions/settings.ts @@ -1,4 +1,4 @@ -### +/* Copyright 2016-2017 Resin.io Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,23 +12,27 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -### +*/ -exports.list = - signature: 'settings' - description: 'print current settings' - help: ''' - Use this command to display detected settings +import { CommandDefinition } from 'capitano'; - Examples: +export const list: CommandDefinition = { + signature: 'settings', + description: 'print current settings', + help: `\ +Use this command to display detected settings - $ resin settings - ''' - action: (params, options, done) -> - resin = require('resin-sdk-preconfigured') - prettyjson = require('prettyjson') +Examples: - resin.settings.getAll() + $ resin settings\ +`, + async action(_params, _options, done) { + const resin = (await import('resin-sdk')).fromSharedOptions(); + const prettyjson = await import('prettyjson'); + + return resin.settings.getAll() .then(prettyjson.render) .then(console.log) - .nodeify(done) + .nodeify(done); + }, +}; diff --git a/lib/actions/sync.coffee b/lib/actions/sync.ts similarity index 86% rename from lib/actions/sync.coffee rename to lib/actions/sync.ts index 9823f35b..dcac6e4f 100644 --- a/lib/actions/sync.coffee +++ b/lib/actions/sync.ts @@ -1,4 +1,4 @@ -### +/* Copyright 2016-2017 Resin.io Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,6 +12,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -### +*/ -module.exports = require('resin-sync').capitano('resin-cli') +import * as ResinSync from 'resin-sync'; +export = ResinSync.capitano('resin-cli'); diff --git a/lib/app.coffee b/lib/app.coffee index 064877d7..66ae36ba 100644 --- a/lib/app.coffee +++ b/lib/app.coffee @@ -56,7 +56,6 @@ globalTunnel.initialize(proxy) # TODO: make this a feature of capitano https://github.com/resin-io/capitano/issues/48 global.PROXY_CONFIG = globalTunnel.proxyConfig -_ = require('lodash') Promise = require('bluebird') capitano = require('capitano') capitanoExecuteAsync = Promise.promisify(capitano.execute) diff --git a/lib/config.ts b/lib/config.ts index 83351520..8d5e0918 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1 +1 @@ -exports.sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239' +export const sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239'; diff --git a/lib/errors.coffee b/lib/errors.coffee deleted file mode 100644 index 57ef89ec..00000000 --- a/lib/errors.coffee +++ /dev/null @@ -1,38 +0,0 @@ -### -Copyright 2016-2017 Resin.io - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -### - -chalk = require('chalk') -errors = require('resin-cli-errors') -patterns = require('./utils/patterns') -Raven = require('raven') -Promise = require('bluebird') - -captureException = Promise.promisify(Raven.captureException.bind(Raven)) - -exports.handle = (error) -> - message = errors.interpret(error) - return if not message? - - if process.env.DEBUG - message = error.stack - - patterns.printErrorMessage(message) - - captureException(error) - .timeout(1000) - .catch(-> # Ignore any errors (from error logging, or timeouts) - ).finally -> - process.exit(error.exitCode or 1) diff --git a/lib/errors.ts b/lib/errors.ts new file mode 100644 index 00000000..979f56ad --- /dev/null +++ b/lib/errors.ts @@ -0,0 +1,39 @@ +/* +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import errors = require('resin-cli-errors'); +import patterns = require('./utils/patterns'); +import Raven = require('raven'); +import Promise = require('bluebird'); + +const captureException = Promise.promisify(Raven.captureException, { context: Raven }); + +exports.handle = function(error: any) { + let message = errors.interpret(error); + if ((message == null)) { return; } + + if (process.env.DEBUG) { + message = error.stack; + } + + patterns.printErrorMessage(message); + + return captureException(error) + .timeout(1000) + .catch(function() { + // Ignore any errors (from error logging, or timeouts) + }).finally(() => process.exit(error.exitCode || 1)); +}; diff --git a/lib/events.coffee b/lib/events.coffee deleted file mode 100644 index 54b6e6be..00000000 --- a/lib/events.coffee +++ /dev/null @@ -1,34 +0,0 @@ -_ = require('lodash') -Mixpanel = require('mixpanel') -Raven = require('raven') -Promise = require('bluebird') -resin = require('resin-sdk-preconfigured') -packageJSON = require('../package.json') - -exports.getLoggerInstance = _.memoize -> - return resin.models.config.getMixpanelToken().then(Mixpanel.init) - -exports.trackCommand = (capitanoCommand) -> - capitanoStateGetMatchCommandAsync = Promise.promisify(require('capitano').state.getMatchCommand) - - return Promise.props - resinUrl: resin.settings.get('resinUrl') - username: resin.auth.whoami().catchReturn(undefined) - mixpanel: exports.getLoggerInstance() - .then ({ username, resinUrl, mixpanel }) -> - return capitanoStateGetMatchCommandAsync(capitanoCommand.command).then (command) -> - Raven.mergeContext(user: { - id: username, - username - }) - mixpanel.track "[CLI] #{command.signature.toString()}", - distinct_id: username - argv: process.argv.join(' ') - version: packageJSON.version - node: process.version - arch: process.arch - resinUrl: resinUrl - platform: process.platform - command: capitanoCommand - .timeout(100) - .catchReturn() diff --git a/lib/events.ts b/lib/events.ts new file mode 100644 index 00000000..c5c539fd --- /dev/null +++ b/lib/events.ts @@ -0,0 +1,42 @@ +import * as Capitano from 'capitano'; + +import _ = require('lodash'); +import Mixpanel = require('mixpanel'); +import Raven = require('raven'); +import Promise = require('bluebird'); +import ResinSdk = require('resin-sdk'); +import packageJSON = require('../package.json'); + +const resin = ResinSdk.fromSharedOptions(); +const getMatchCommandAsync = Promise.promisify(Capitano.state.getMatchCommand); +const getMixpanel = _.memoize(() => resin.models.config.getAll().get('mixpanelToken').then(Mixpanel.init)); + +export function trackCommand(capitanoCli: Capitano.Cli) { + return Promise.props({ + resinUrl: resin.settings.get('resinUrl'), + username: resin.auth.whoami().catchReturn(undefined), + mixpanel: getMixpanel(), + }).then(({ username, resinUrl, mixpanel }) => { + return getMatchCommandAsync(capitanoCli.command).then((command) => { + Raven.mergeContext({ + user: { + id: username, + username, + }, + }); + + return mixpanel.track(`[CLI] ${command.signature.toString()}`, { + distinct_id: username, + argv: process.argv.join(' '), + version: packageJSON.version, + node: process.version, + arch: process.arch, + resinUrl, + platform: process.platform, + command: capitanoCli, + }); + }); + }) + .timeout(100) + .catchReturn(undefined); +} diff --git a/lib/utils/config.coffee b/lib/utils/config.coffee deleted file mode 100644 index aa2ff238..00000000 --- a/lib/utils/config.coffee +++ /dev/null @@ -1,97 +0,0 @@ -### -Copyright 2016-2017 Resin.io - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -### - -exports.generateBaseConfig = (application, options) -> - Promise = require('bluebird') - _ = require('lodash') - deviceConfig = require('resin-device-config') - resin = require('resin-sdk-preconfigured') - - options = _.mapValues options, (value, key) -> - if key == 'appUpdatePollInterval' - value * 60 * 1000 - else - value - - Promise.props - userId: resin.auth.getUserId() - username: resin.auth.whoami() - apiUrl: resin.settings.get('apiUrl') - vpnUrl: resin.settings.get('vpnUrl') - registryUrl: resin.settings.get('registryUrl') - deltaUrl: resin.settings.get('deltaUrl') - pubNubKeys: resin.models.config.getPubNubKeys() - mixpanelToken: resin.models.config.getMixpanelToken() - .then (results) -> - deviceConfig.generate - application: application - user: - id: results.userId - username: results.username - endpoints: - api: results.apiUrl - vpn: results.vpnUrl - registry: results.registryUrl - delta: results.deltaUrl - pubnub: results.pubNubKeys - mixpanel: - token: results.mixpanelToken - , options - -exports.generateApplicationConfig = (application, options) -> - exports.generateBaseConfig(application, options) - .tap (config) -> - authenticateWithApplicationKey(config, application.id) - -exports.generateDeviceConfig = (device, deviceApiKey, options) -> - resin = require('resin-sdk-preconfigured') - - resin.models.application.get(device.application_name) - .then (application) -> - exports.generateBaseConfig(application, options) - .tap (config) -> - # Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain - # the expected version for this config and generate one when we know it's safe, - # but instead for now we fall back to app keys unless the user has explicitly opted in. - if deviceApiKey? - authenticateWithDeviceKey(config, device.uuid, deviceApiKey) - else - authenticateWithApplicationKey(config, application.id) - .then (config) -> - # Associate a device, to prevent the supervisor - # from creating another one on its own. - config.registered_at = Math.floor(Date.now() / 1000) - config.deviceId = device.id - config.uuid = device.uuid - - return config - -authenticateWithApplicationKey = (config, applicationNameOrId) -> - resin = require('resin-sdk-preconfigured') - resin.models.application.generateApiKey(applicationNameOrId) - .then (apiKey) -> - config.apiKey = apiKey - return config - -authenticateWithDeviceKey = (config, uuid, customDeviceApiKey) -> - Promise = require('bluebird') - resin = require('resin-sdk-preconfigured') - - Promise.try -> - customDeviceApiKey || resin.models.device.generateDeviceKey(uuid) - .then (deviceApiKey) -> - config.deviceApiKey = deviceApiKey - return config diff --git a/lib/utils/config.ts b/lib/utils/config.ts new file mode 100644 index 00000000..5a797197 --- /dev/null +++ b/lib/utils/config.ts @@ -0,0 +1,108 @@ +/* +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import Promise = require('bluebird'); +import ResinSdk = require('resin-sdk'); +import _ = require('lodash'); +import deviceConfig = require('resin-device-config'); + +const resin = ResinSdk.fromSharedOptions(); + +export function generateBaseConfig(application: ResinSdk.Application, options: {}) { + options = _.mapValues(options, function(value, key) { + if (key === 'appUpdatePollInterval') { + return value * 60 * 1000; + } else { + return value; + } + }); + + return Promise.props({ + userId: resin.auth.getUserId(), + username: resin.auth.whoami(), + apiUrl: resin.settings.get('apiUrl'), + vpnUrl: resin.settings.get('vpnUrl'), + registryUrl: resin.settings.get('registryUrl'), + deltaUrl: resin.settings.get('deltaUrl'), + apiConfig: resin.models.config.getAll(), + }).then((results) => { + return deviceConfig.generate({ + application, + user: { + id: results.userId, + username: results.username, + }, + endpoints: { + api: results.apiUrl, + vpn: results.vpnUrl, + registry: results.registryUrl, + delta: results.deltaUrl, + }, + pubnub: results.apiConfig.pubnub, + mixpanel: { + token: results.apiConfig.mixpanelToken, + }, + }, options); + }); +} + +export function generateApplicationConfig(application: ResinSdk.Application, options: {}) { + return generateBaseConfig(application, options) + .tap(config => addApplicationKey(config, application.id)); +} + +export function generateDeviceConfig( + device: ResinSdk.Device & { application_name: string }, + deviceApiKey: string | null, + options: {}, +) { + return resin.models.application.get(device.application_name) + .then(application => { + return generateBaseConfig(application, options) + .tap((config) => { + // Device API keys are only safe for ResinOS 2.0.3+. We could somehow obtain + // the expected version for this config and generate one when we know it's safe, + // but instead for now we fall back to app keys unless the user has explicitly opted in. + if (deviceApiKey) { + return addDeviceKey(config, device.uuid, deviceApiKey); + } else { + return addApplicationKey(config, application.id); + } + }); + }).then((config) => { + // Associate a device, to prevent the supervisor + // from creating another one on its own. + config.registered_at = Math.floor(Date.now() / 1000); + config.deviceId = device.id; + config.uuid = device.uuid; + + return config; + }); +} + +function addApplicationKey(config: any, applicationNameOrId: string | number) { + return resin.models.application.generateApiKey(applicationNameOrId) + .tap((apiKey) => { + config.apiKey = apiKey; + }); +} + +function addDeviceKey(config: any, uuid: string, customDeviceApiKey: string) { + return Promise.try(() => { + return customDeviceApiKey || resin.models.device.generateDeviceKey(uuid); + }).tap((deviceApiKey) => { + config.deviceApiKey = deviceApiKey; + }); +} diff --git a/lib/utils/helpers.coffee b/lib/utils/helpers.coffee deleted file mode 100644 index 206d1a85..00000000 --- a/lib/utils/helpers.coffee +++ /dev/null @@ -1,135 +0,0 @@ -### -Copyright 2016-2017 Resin.io - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -### - -Promise = require('bluebird') - -exports.getGroupDefaults = (group) -> - _ = require('lodash') - - return _.chain(group) - .get('options') - .map (question) -> - return [ question.name, question.default ] - .fromPairs() - .value() - -exports.stateToString = (state) -> - _str = require('underscore.string') - chalk = require('chalk') - - 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) -> - _ = require('lodash') - os = require('os') - - if os.platform() isnt 'win32' - console.log('If asked please type your computer password to continue') - - command = _.union(_.take(process.argv, 2), command) - presidentExecuteAsync = Promise.promisify(require('president').execute) - return presidentExecuteAsync(command) - -exports.getManifest = (image, deviceType) -> - rindle = require('rindle') - imagefs = require('resin-image-fs') - resin = require('resin-sdk-preconfigured') - - # Attempt to read manifest from the first - # partition, but fallback to the API if - # we encounter any errors along the way. - imagefs.read - image: image - partition: - primary: 1 - path: '/device-type.json' - .then(rindle.extractAsync) - .then(JSON.parse) - .catch -> - resin.models.device.getManifestBySlug(deviceType) - -exports.osProgressHandler = (step) -> - rindle = require('rindle') - visuals = require('resin-cli-visuals') - - step.on('stdout', process.stdout.write.bind(process.stdout)) - step.on('stderr', process.stderr.write.bind(process.stderr)) - - step.on 'state', (state) -> - return if state.operation.command is 'burn' - console.log(exports.stateToString(state)) - - progressBars = - write: new visuals.Progress('Writing Device OS') - check: new visuals.Progress('Validating Device OS') - - step.on 'burn', (state) -> - progressBars[state.type].update(state) - - return rindle.wait(step) - -exports.getAppInfo = (application) -> - resin = require('resin-sdk-preconfigured') - _ = require('lodash') - Promise.join( - getApplication(application), - resin.models.config.getDeviceTypes(), - (app, config) -> - config = _.find(config, 'slug': app.device_type) - if !config? - throw new Error('Could not read application information!') - app.arch = config.arch - return app - ) - -getApplication = (application) -> - resin = require('resin-sdk-preconfigured') - - # Check for an app of the form `user/application`, and send - # this off to a special handler (before importing any modules) - if (match = /(\w+)\/(\w+)/.exec(application)) - return resin.models.application.getAppWithOwner(match[2], match[1]) - - return resin.models.application.get(application) - -# A function to reliably execute a command -# in all supported operating systems, including -# different Windows environments like `cmd.exe` -# and `Cygwin`. -exports.getSubShellCommand = (command) -> - os = require('os') - - if os.platform() is 'win32' - return { - program: 'cmd.exe' - args: [ '/s', '/c', command ] - } - else - return { - program: '/bin/sh' - args: [ '-c', command ] - } diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts new file mode 100644 index 00000000..b0bc8e3c --- /dev/null +++ b/lib/utils/helpers.ts @@ -0,0 +1,150 @@ +/* +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import os = require('os'); +import Promise = require('bluebird'); +import _ = require('lodash'); +import chalk from 'chalk'; +import rindle = require('rindle'); +import imagefs = require('resin-image-fs'); +import visuals = require('resin-cli-visuals'); +import ResinSdk = require('resin-sdk'); + +import { execute } from 'president'; +import { InitializeEmitter, OperationState } from 'resin-device-init'; + +const extractStreamAsync = Promise.promisify(rindle.extract); +const waitStreamAsync = Promise.promisify(rindle.wait); +const presidentExecuteAsync = Promise.promisify(execute); + +const resin = ResinSdk.fromSharedOptions(); + +export function getGroupDefaults( + group: { options: { name: string, default?: string }[] }, +): { [name: string]: string | undefined } { + return _.chain(group) + .get('options') + .map((question) => [ question.name, question.default ]) + .fromPairs() + .value(); +} + +export function stateToString(state: OperationState) { + const percentage = _.padStart(`${state.percentage}`, 3, '0'); + const 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.command}`); + } +} + +export function sudo(command: string[]) { + if (os.platform() !== 'win32') { + console.log('If asked please type your computer password to continue'); + } + + command = _.union(_.take(process.argv, 2), command); + + return presidentExecuteAsync(command); +} + +export function getManifest(image: string, deviceType: string): Promise { + // Attempt to read manifest from the first + // partition, but fallback to the API if + // we encounter any errors along the way. + return imagefs.read({ + image, + partition: { + primary: 1, + }, + path: '/device-type.json', + }) + .then(extractStreamAsync) + .then(JSON.parse) + .catch(() => resin.models.device.getManifestBySlug(deviceType)); +} + +export function osProgressHandler(step: InitializeEmitter) { + step.on('stdout', process.stdout.write.bind(process.stdout)); + step.on('stderr', process.stderr.write.bind(process.stderr)); + + step.on('state', function(state) { + if (state.operation.command === 'burn') { return; } + console.log(exports.stateToString(state)); + }); + + const progressBars = { + write: new visuals.Progress('Writing Device OS'), + check: new visuals.Progress('Validating Device OS'), + }; + + step.on('burn', state => progressBars[state.type].update(state)); + + return waitStreamAsync(step); +} + +export function getArchAndDeviceType(applicationName: string): Promise<{ arch: string, device_type: string }> { + return Promise.join( + getApplication(applicationName), + resin.models.config.getDeviceTypes(), + function (app, deviceTypes) { + const config = _.find(deviceTypes, { slug: app.device_type }); + + if (!config) { + throw new Error('Could not read application information!'); + } + + return { device_type: app.device_type, arch: config.arch }; + }, + ); +} + +function getApplication(applicationName: string) { + // Check for an app of the form `user/application`, and send + // that off to a special handler (before importing any modules) + const match = /(\w+)\/(\w+)/.exec(applicationName); + + if (match) { + return resin.models.application.getAppByOwner(match[2], match[1]); + } + + return resin.models.application.get(applicationName); +} + +// A function to reliably execute a command +// in all supported operating systems, including +// different Windows environments like `cmd.exe` +// and `Cygwin`. +export function getSubShellCommand(command: string) { + if (os.platform() === 'win32') { + return { + program: 'cmd.exe', + args: [ '/s', '/c', command ], + }; + } else { + return { + program: '/bin/sh', + args: [ '-c', command ], + }; + } +} diff --git a/lib/utils/logger.coffee b/lib/utils/logger.coffee deleted file mode 100644 index d9de3312..00000000 --- a/lib/utils/logger.coffee +++ /dev/null @@ -1,46 +0,0 @@ -eol = require('os').EOL - -module.exports = class Logger - constructor: -> - { StreamLogger } = require('resin-stream-logger') - colors = require('colors') - _ = require('lodash') - - logger = new StreamLogger() - logger.addPrefix('build', colors.blue('[Build]')) - logger.addPrefix('info', colors.cyan('[Info]')) - logger.addPrefix('debug', colors.magenta('[Debug]')) - logger.addPrefix('success', colors.green('[Success]')) - logger.addPrefix('warn', colors.yellow('[Warn]')) - logger.addPrefix('error', colors.red('[Error]')) - - @streams = - build: logger.createLogStream('build'), - info: logger.createLogStream('info'), - debug: logger.createLogStream('debug'), - success: logger.createLogStream('success'), - warn: logger.createLogStream('warn'), - error: logger.createLogStream('error') - - _.mapKeys @streams, (stream, key) -> - if key isnt 'debug' - stream.pipe(process.stdout) - else - stream.pipe(process.stdout) if process.env.DEBUG? - - @formatMessage = logger.formatWithPrefix.bind(logger) - - logInfo: (msg) -> - @streams.info.write(msg + eol) - - logDebug: (msg) -> - @streams.debug.write(msg + eol) - - logSuccess: (msg) -> - @streams.success.write(msg + eol) - - logWarn: (msg) -> - @streams.warn.write(msg + eol) - - logError: (msg) -> - @streams.error.write(msg + eol) diff --git a/lib/utils/logger.ts b/lib/utils/logger.ts new file mode 100644 index 00000000..f14c699e --- /dev/null +++ b/lib/utils/logger.ts @@ -0,0 +1,64 @@ +import { EOL as eol } from 'os'; +import _ = require('lodash'); +import chalk from 'chalk'; +import { StreamLogger } from 'resin-stream-logger'; + +export class Logger { + public streams: { + build: NodeJS.ReadWriteStream; + info: NodeJS.ReadWriteStream; + debug: NodeJS.ReadWriteStream; + success: NodeJS.ReadWriteStream; + warn: NodeJS.ReadWriteStream; + error: NodeJS.ReadWriteStream; + }; + + public formatMessage: (name: string, message: string) => string; + + constructor() { + const logger = new StreamLogger(); + logger.addPrefix('build', chalk.blue('[Build]')); + logger.addPrefix('info', chalk.cyan('[Info]')); + logger.addPrefix('debug', chalk.magenta('[Debug]')); + logger.addPrefix('success', chalk.green('[Success]')); + logger.addPrefix('warn', chalk.yellow('[Warn]')); + logger.addPrefix('error', chalk.red('[Error]')); + + this.streams = { + build: logger.createLogStream('build'), + info: logger.createLogStream('info'), + debug: logger.createLogStream('debug'), + success: logger.createLogStream('success'), + warn: logger.createLogStream('warn'), + error: logger.createLogStream('error'), + }; + + _.forEach(this.streams, function(stream, key) { + if (key !== 'debug' || process.env.DEBUG) { + stream.pipe(process.stdout); + } + }); + + this.formatMessage = logger.formatWithPrefix.bind(logger); + } + + logInfo(msg: string) { + return this.streams.info.write(msg + eol); + } + + logDebug(msg: string) { + return this.streams.debug.write(msg + eol); + } + + logSuccess(msg: string) { + return this.streams.success.write(msg + eol); + } + + logWarn(msg: string) { + return this.streams.warn.write(msg + eol); + } + + logError(msg: string) { + return this.streams.error.write(msg + eol); + } +} diff --git a/lib/utils/messages.coffee b/lib/utils/messages.coffee deleted file mode 100644 index ef0b1785..00000000 --- a/lib/utils/messages.coffee +++ /dev/null @@ -1,22 +0,0 @@ -exports.reachingOut = ''' - If you need help, or just want to say hi, don't hesitate in reaching out at: - - GitHub: https://github.com/resin-io/resin-cli/issues/new - Forums: https://forums.resin.io -''' - -exports.getHelp = ''' - If you need help, don't hesitate in contacting us at: - - GitHub: https://github.com/resin-io/resin-cli/issues/new - Forums: https://forums.resin.io -''' - -exports.resinAsciiArt = ''' - ______ _ _ - | ___ \\ (_) (_) - | |_/ /___ ___ _ _ __ _ ___ - | // _ \\/ __| | '_ \\ | |/ _ \\ - | |\\ \\ __/\\__ \\ | | | |_| | (_) | - \\_| \\_\\___||___/_|_| |_(_)_|\\___/ -''' diff --git a/lib/utils/messages.ts b/lib/utils/messages.ts new file mode 100644 index 00000000..113fd7ae --- /dev/null +++ b/lib/utils/messages.ts @@ -0,0 +1,22 @@ +export const reachingOut = `\ +If you need help, or just want to say hi, don't hesitate in reaching out at: + + GitHub: https://github.com/resin-io/resin-cli/issues/new + Forums: https://forums.resin.io\ +`; + +export const getHelp = `\ +If you need help, don't hesitate in contacting us at: + + GitHub: https://github.com/resin-io/resin-cli/issues/new + Forums: https://forums.resin.io\ +`; + +export const resinAsciiArt = `\ +______ _ _ +| ___ \\ (_) (_) +| |_/ /___ ___ _ _ __ _ ___ +| // _ \\/ __| | '_ \\ | |/ _ \\ +| |\\ \\ __/\\__ \\ | | | |_| | (_) | +\\_| \\_\\___||___/_|_| |_(_)_|\\___/\ +`; diff --git a/lib/utils/patterns.coffee b/lib/utils/patterns.coffee deleted file mode 100644 index fc586abd..00000000 --- a/lib/utils/patterns.coffee +++ /dev/null @@ -1,181 +0,0 @@ -### -Copyright 2016-2017 Resin.io - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -### - -_ = require('lodash') -Promise = require('bluebird') -form = require('resin-cli-form') -visuals = require('resin-cli-visuals') -resin = require('resin-sdk-preconfigured') -chalk = require('chalk') -validation = require('./validation') -messages = require('./messages') - -exports.authenticate = (options) -> - return 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 (error) -> - resin.auth.logout().then -> - if error.name is 'ResinRequestError' and error.statusCode is 401 - throw new Error('Invalid two factor authentication code') - throw error - -exports.askLoginType = -> - return form.ask - message: 'How would you like to login?' - name: 'loginType' - type: 'list' - choices: [ - name: 'Web authorization (recommended)' - value: 'web' - , - name: 'Credentials' - value: 'credentials' - , - name: 'Authentication token' - value: 'token' - , - name: 'I don\'t have a Resin account!' - value: 'register' - ] - -exports.selectDeviceType = -> - resin.models.device.getSupportedDeviceTypes().then (deviceTypes) -> - return form.ask - message: 'Device Type' - type: 'list' - choices: deviceTypes - -exports.confirm = (yesOption, message, yesMessage) -> - Promise.try -> - if yesOption - console.log(yesMessage) if yesMessage - return true - return form.ask - message: message - type: 'confirm' - default: false - .then (confirmed) -> - if not confirmed - throw new Error('Aborted') - -exports.selectApplication = (filter) -> - resin.models.application.hasAny().then (hasAnyApplications) -> - if not hasAnyApplications - throw new Error('You don\'t have any applications') - - return resin.models.application.getAll() - .filter(filter or _.constant(true)) - .then (applications) -> - return form.ask - message: 'Select an application' - type: 'list' - choices: _.map applications, (application) -> - return { - name: "#{application.app_name} (#{application.device_type})" - value: application.app_name - } - -exports.selectOrCreateApplication = -> - resin.models.application.hasAny().then (hasAnyApplications) -> - return if not hasAnyApplications - resin.models.application.getAll().then (applications) -> - applications = _.map applications, (application) -> - return { - name: "#{application.app_name} (#{application.device_type})" - value: application.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("The device **#{deviceName}** is online!") - 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.inferOrSelectDevice = (preferredUuid) -> - resin.models.device.getAll() - .filter (device) -> - device.is_online - .then (onlineDevices) -> - if _.isEmpty(onlineDevices) - throw new Error('You don\'t have any devices online') - - return form.ask - message: 'Select a device' - type: 'list' - default: if preferredUuid in _.map(onlineDevices, 'uuid') then preferredUuid else onlineDevices[0].uuid - choices: _.map onlineDevices, (device) -> - return { - name: "#{device.name or 'Untitled'} (#{device.uuid.slice(0, 7)})" - value: device.uuid - } - -exports.printErrorMessage = (message) -> - console.error(chalk.red(message)) - console.error(chalk.red("\n#{messages.getHelp}\n")) - -exports.expectedError = (message) -> - if message instanceof Error - message = message.message - exports.printErrorMessage(message) - process.exit(1) diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts new file mode 100644 index 00000000..e2daa81c --- /dev/null +++ b/lib/utils/patterns.ts @@ -0,0 +1,234 @@ +/* +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import _ = require('lodash'); +import Promise = require('bluebird'); +import form = require('resin-cli-form'); +import visuals = require('resin-cli-visuals'); +import ResinSdk = require('resin-sdk'); +import chalk from 'chalk'; +import validation = require('./validation'); +import messages = require('./messages'); + +const resin = ResinSdk.fromSharedOptions(); + +export function authenticate(options: {}): Promise { + return 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: boolean) => { + if (isTwoFactorAuthPassed) { return; } + + return form.ask({ + message: 'Two factor auth challenge:', + name: 'code', + type: 'input', + }) + .then(resin.auth.twoFactor.challenge) + .catch((error: any) => { + return resin.auth.logout().then(() => { + if ((error.name === 'ResinRequestError') && (error.statusCode === 401)) { + throw new Error('Invalid two factor authentication code'); + } + throw error; + }); + }); + }); +} + +export function askLoginType() { + return form.ask({ + message: 'How would you like to login?', + name: 'loginType', + type: 'list', + choices: [{ + name: 'Web authorization (recommended)', + value: 'web', + }, { + name: 'Credentials', + value: 'credentials', + }, { + name: 'Authentication token', + value: 'token', + }, { + name: 'I don\'t have a Resin account!', + value: 'register', + }], + }); +} + +export function selectDeviceType() { + return resin.models.device.getSupportedDeviceTypes() + .then(deviceTypes => { + return form.ask({ + message: 'Device Type', + type: 'list', + choices: deviceTypes, + }); + }); +} + +export function confirm(yesOption: string, message: string, yesMessage: string) { + return Promise.try(function () { + if (yesOption) { + if (yesMessage) { console.log(yesMessage); } + return true; + } + + return form.ask({ + message, + type: 'confirm', + default: false, + }); + }).then(function(confirmed) { + if (!confirmed) { + throw new Error('Aborted'); + } + }); +} + +export function selectApplication(filter: (app: ResinSdk.Application) => boolean) { + resin.models.application.hasAny().then(function(hasAnyApplications) { + if (!hasAnyApplications) { + throw new Error('You don\'t have any applications'); + } + + return resin.models.application.getAll(); + }) + .filter(filter || _.constant(true)) + .then(applications => { + return form.ask({ + message: 'Select an application', + type: 'list', + choices: _.map(applications, application => + ({ + name: `${application.app_name} (${application.device_type})`, + value: application.app_name, + }), + )}); + }); +} + +export function selectOrCreateApplication() { + return resin.models.application.hasAny().then((hasAnyApplications) => { + if (!hasAnyApplications) return; + + return resin.models.application.getAll().then((applications) => { + const appOptions = _.map< + ResinSdk.Application, + { name: string, value: string | null } + >(applications, application => ({ + name: `${application.app_name} (${application.device_type})`, + value: application.app_name, + })); + + appOptions.unshift({ + name: 'Create a new application', + value: null, + }); + + return form.ask({ + message: 'Select an application', + type: 'list', + choices: appOptions, + }); + }); + }).then((application) => { + if (application) { + return application; + } + + return form.ask({ + message: 'Choose a Name for your new application', + type: 'input', + validate: validation.validateApplicationName, + }); + }); +} + +export function awaitDevice(uuid: string) { + return resin.models.device.getName(uuid) + .then((deviceName) => { + const spinner = new visuals.Spinner(`Waiting for ${deviceName} to come online`); + + const poll = (): Promise => { + return resin.models.device.isOnline(uuid) + .then(function(isOnline) { + if (isOnline) { + spinner.stop(); + console.info(`The device **${deviceName}** is online!`); + 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...`); + return poll().return(uuid); + }); +} + +export function inferOrSelectDevice(preferredUuid: string) { + return resin.models.device.getAll() + .filter((device) => device.is_online) + .then((onlineDevices) => { + if (_.isEmpty(onlineDevices)) { + throw new Error('You don\'t have any devices online'); + } + + const defaultUuid = _.map(onlineDevices, 'uuid').includes(preferredUuid) ? + preferredUuid : + onlineDevices[0].uuid; + + return form.ask({ + message: 'Select a device', + type: 'list', + default: defaultUuid, + choices: _.map(onlineDevices, device => ({ + name: `${device.name || 'Untitled'} (${device.uuid.slice(0, 7)})`, + value: device.uuid, + }), + )}); + }); +} + +export function printErrorMessage(message: string) { + console.error(chalk.red(message)); + console.error(chalk.red(`\n${messages.getHelp}\n`)); +} + +export function expectedError(message: string | Error) { + if (message instanceof Error) { + ({ message } = message); + } + + printErrorMessage(message); + process.exit(1); +} diff --git a/lib/utils/plugins.coffee b/lib/utils/plugins.ts similarity index 50% rename from lib/utils/plugins.coffee rename to lib/utils/plugins.ts index f7addd9b..1896c7b9 100644 --- a/lib/utils/plugins.coffee +++ b/lib/utils/plugins.ts @@ -1,4 +1,4 @@ -### +/* Copyright 2016-2017 Resin.io Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,18 +12,22 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -### +*/ -nplugm = require('nplugm') -_ = require('lodash') -capitano = require('capitano') -patterns = require('./patterns') +import nplugm = require('nplugm'); +import _ = require('lodash'); +import capitano = require('capitano'); +import 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) +export function register(regex: RegExp): Promise { + return nplugm.list(regex).map(async function(plugin: any) { + const command = await import(plugin); + command.plugin = true; + if (!_.isArray(command)) { + return capitano.command(command); + } + return _.each(command, capitano.command); + }).catch((error: Error) => { + return patterns.printErrorMessage(error.message); + }); +} diff --git a/lib/utils/streams.coffee b/lib/utils/streams.coffee deleted file mode 100644 index 6ef7f1d0..00000000 --- a/lib/utils/streams.coffee +++ /dev/null @@ -1,17 +0,0 @@ -exports.buffer = (stream, bufferFile) -> - Promise = require('bluebird') - fs = require('fs') - - fileWriteStream = fs.createWriteStream(bufferFile) - - new Promise (resolve, reject) -> - stream - .on('error', reject) - .on('end', resolve) - .pipe(fileWriteStream) - .then -> - new Promise (resolve, reject) -> - fs.createReadStream(bufferFile) - .on 'open', -> - resolve(this) - .on('error', reject) diff --git a/lib/utils/streams.ts b/lib/utils/streams.ts new file mode 100644 index 00000000..aa580e8f --- /dev/null +++ b/lib/utils/streams.ts @@ -0,0 +1,19 @@ +export async function buffer(stream: NodeJS.ReadableStream, bufferFile: string) { + const Promise = await import('bluebird'); + const fs = await import('fs'); + + const fileWriteStream = fs.createWriteStream(bufferFile); + + return new Promise(function(resolve, reject) { + stream + .on('error', reject) + .on('end', resolve) + .pipe(fileWriteStream); + }).then(() => new Promise(function(resolve, reject) { + const stream = fs.createReadStream(bufferFile); + + stream + .on('open', () => resolve(stream)) + .on('error', reject); + })); +} diff --git a/lib/utils/update.coffee b/lib/utils/update.coffee deleted file mode 100644 index 11347eb3..00000000 --- a/lib/utils/update.coffee +++ /dev/null @@ -1,40 +0,0 @@ -### -Copyright 2016-2017 Resin.io - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -### - -updateNotifier = require('update-notifier') -isRoot = require('is-root') -packageJSON = require('../../package.json') - -# Check for an update once a day. 1 day granularity should be -# enough, rather than every run. -resinUpdateInterval = 1000 * 60 * 60 * 24 * 1 - -# `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 - updateCheckInterval: resinUpdateInterval - -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') diff --git a/lib/utils/update.ts b/lib/utils/update.ts new file mode 100644 index 00000000..a34624e6 --- /dev/null +++ b/lib/utils/update.ts @@ -0,0 +1,47 @@ +/* +Copyright 2016-2017 Resin.io + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as UpdateNotifier from 'update-notifier'; +import isRoot = require('is-root'); +import packageJSON = require('../../package.json'); + +// Check for an update once a day. 1 day granularity should be +// enough, rather than every run. +const resinUpdateInterval = 1000 * 60 * 60 * 24 * 1; + +let notifier: UpdateNotifier.UpdateNotifier; + +// `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 (!isRoot()) { + notifier = UpdateNotifier({ + pkg: packageJSON, + updateCheckInterval: resinUpdateInterval, + }); +} + +export function notify() { + if (!notifier) { + return; + } + + notifier.notify({ defer: false }); + + if (notifier.update != null) { + console.log('Notice that you might need administrator privileges depending on your setup\n'); + } +} diff --git a/lib/utils/validation.coffee b/lib/utils/validation.ts similarity index 53% rename from lib/utils/validation.coffee rename to lib/utils/validation.ts index 2901a982..010397e9 100644 --- a/lib/utils/validation.coffee +++ b/lib/utils/validation.ts @@ -1,4 +1,4 @@ -### +/* Copyright 2016-2017 Resin.io Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,24 +12,30 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -### +*/ -validEmail = require('@resin.io/valid-email') +import validEmail = require('@resin.io/valid-email'); -exports.validateEmail = (input) -> - if not validEmail(input) - return 'Email is not valid' +export function validateEmail(input: string) { + if (!validEmail(input)) { + return 'Email is not valid'; + } - return true + return true; +} -exports.validatePassword = (input) -> - if input.length < 8 - return 'Password should be 8 characters long' +export function validatePassword(input: string) { + if (input.length < 8) { + return 'Password should be 8 characters long'; + } - return true + return true; +} -exports.validateApplicationName = (input) -> - if input.length < 4 - return 'The application name should be at least 4 characters' +export function validateApplicationName(input: string) { + if (input.length < 4) { + return 'The application name should be at least 4 characters'; + } - return true + return true; +} diff --git a/package.json b/package.json index e76b2770..7d581c3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resin-cli", - "version": "6.12.2", + "version": "6.12.3", "description": "The official resin.io CLI tool", "main": "./build/actions/index.js", "homepage": "https://github.com/resin-io/resin-cli", @@ -31,15 +31,15 @@ "scripts": { "prebuild": "rimraf build/ build-bin/ build-zip/", "build": "npm run build:src && npm run build:bin", - "build:src": "gulp build && tsc && npm run doc", + "build:src": "npm run lint && gulp build && tsc && npm run build:doc", + "build:doc": "mkdirp doc/ && ts-node extras/capitanodoc/index.ts > doc/cli.markdown", "build:bin": "ts-node --type-check -P automation automation/build-bin.ts", "release": "npm run build && ts-node --type-check -P automation automation/deploy-bin.ts", "pretest": "npm run build", "test": "gulp test", "ci": "npm run test && catch-uncommitted", - "doc": "mkdirp doc/ && coffee extras/capitanodoc/index.coffee > doc/cli.markdown", "watch": "gulp watch", - "lint": "gulp lint", + "lint": "resin-lint lib/ tests/ && resin-lint --typescript automation/ extras/ lib/ typings/ tests/", "prepublish": "require-npm4-to-publish", "prepublishOnly": "npm run build" }, @@ -54,15 +54,18 @@ }, "devDependencies": { "@types/archiver": "^2.0.1", + "@types/bluebird": "^3.5.19", "@types/fs-extra": "^5.0.0", + "@types/is-root": "^1.0.0", "@types/mkdirp": "^0.5.2", + "@types/prettyjson": "0.0.28", + "@types/raven": "^2.1.2", "catch-uncommitted": "^1.0.0", "ent": "^2.2.0", "filehound": "^1.16.2", "fs-extra": "^5.0.0", "gulp": "^3.9.0", "gulp-coffee": "^2.2.0", - "gulp-coffeelint": "^0.6.0", "gulp-inline-source": "^2.1.0", "gulp-mocha": "^2.0.0", "gulp-shell": "^0.5.2", @@ -70,8 +73,9 @@ "pkg": "^4.3.0-beta.1", "publish-release": "^1.3.3", "require-npm4-to-publish": "^1.0.0", + "resin-lint": "^1.5.0", "ts-node": "^4.0.1", - "typescript": "^2.6.1" + "typescript": "2.4.0" }, "dependencies": { "@resin.io/valid-email": "^0.1.0", @@ -82,7 +86,7 @@ "bluebird": "^3.3.3", "body-parser": "^1.14.1", "capitano": "^1.7.0", - "chalk": "^1.1.3", + "chalk": "^2.3.0", "coffee-script": "^1.12.6", "columnify": "^1.5.2", "denymount": "^2.2.0", @@ -129,7 +133,7 @@ "resin-sdk": "^7.0.0", "resin-sdk-preconfigured": "^6.9.0", "resin-settings-client": "^3.6.1", - "resin-stream-logger": "^0.0.4", + "resin-stream-logger": "^0.1.0", "resin-sync": "^9.2.3", "rimraf": "^2.4.3", "rindle": "^1.0.0", diff --git a/tsconfig.json b/tsconfig.json index fc436833..422de22a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,9 +8,20 @@ "noUnusedParameters": true, "preserveConstEnums": true, "removeComments": true, - "sourceMap": true + "sourceMap": true, + "lib": [ + // es5 defaults: + "dom", + "es5", + "scripthost", + // some specific es6 bits we're sure are safe: + "es2015.collection", + "es2015.iterable", + "es2016.array.include" + ] }, "include": [ + "./typings/*.d.ts", "./lib/**/*.ts" ] } diff --git a/typings/@resin-valid-email.d.ts b/typings/@resin-valid-email.d.ts new file mode 100644 index 00000000..7d9183ee --- /dev/null +++ b/typings/@resin-valid-email.d.ts @@ -0,0 +1 @@ +declare module '@resin.io/valid-email'; diff --git a/typings/capitano.d.ts b/typings/capitano.d.ts new file mode 100644 index 00000000..1958a6db --- /dev/null +++ b/typings/capitano.d.ts @@ -0,0 +1,53 @@ +declare module 'capitano' { + export function parse(argv: string[]): Cli; + + export interface Cli { + command: string; + options: {}; + global: {}; + } + + export interface OptionDefinition { + signature: string; + description: string; + parameter?: string; + boolean?: boolean; + alias?: string | string[]; + } + + export interface CommandDefinition

{ + signature: string; + description: string; + help: string; + options?: OptionDefinition[]; + permission?: 'user'; + action(params: P, options: O, done: () => void): void; + } + + export interface Command { + signature: Signature; + options: Option[]; + isWildcard(): boolean; + } + + export interface Signature { + hasParameters(): boolean; + hasVariadicParameters(): boolean; + isWildcard(): boolean; + allowsStdin(): boolean; + } + + export interface Option { + signature: Signature; + alias: string | string[]; + boolean: boolean; + parameter: string; + required: boolean | string; + } + + export function command(command: CommandDefinition): void; + + export const state: { + getMatchCommand: (signature: string, callback: (e: Error, cmd: Command) => void) => void + }; +} diff --git a/typings/mixpanel.d.ts b/typings/mixpanel.d.ts new file mode 100644 index 00000000..271b8d30 --- /dev/null +++ b/typings/mixpanel.d.ts @@ -0,0 +1 @@ +declare module 'mixpanel'; diff --git a/typings/nplugm.d.ts b/typings/nplugm.d.ts new file mode 100644 index 00000000..ed56b8dd --- /dev/null +++ b/typings/nplugm.d.ts @@ -0,0 +1,4 @@ +declare module 'nplugm' { + import Promise = require('bluebird'); + export function list(regexp: RegExp): Promise>; +} diff --git a/typings/package.json.d.ts b/typings/package.json.d.ts new file mode 100644 index 00000000..7f887f81 --- /dev/null +++ b/typings/package.json.d.ts @@ -0,0 +1,4 @@ +declare module '*/package.json' { + export const name: string; + export const version: string; +} diff --git a/typings/president.d.ts b/typings/president.d.ts new file mode 100644 index 00000000..485052a6 --- /dev/null +++ b/typings/president.d.ts @@ -0,0 +1,3 @@ +declare module 'president' { + export function execute(command: string[], callback: (err: Error) => void): void; +} diff --git a/typings/resin-cli-errors.d.ts b/typings/resin-cli-errors.d.ts new file mode 100644 index 00000000..b62af69f --- /dev/null +++ b/typings/resin-cli-errors.d.ts @@ -0,0 +1 @@ +declare module 'resin-cli-errors'; diff --git a/typings/resin-cli-form.d.ts b/typings/resin-cli-form.d.ts new file mode 100644 index 00000000..073592bd --- /dev/null +++ b/typings/resin-cli-form.d.ts @@ -0,0 +1 @@ +declare module 'resin-cli-form'; diff --git a/typings/resin-cli-visuals.d.ts b/typings/resin-cli-visuals.d.ts new file mode 100644 index 00000000..f1b2e48f --- /dev/null +++ b/typings/resin-cli-visuals.d.ts @@ -0,0 +1 @@ +declare module 'resin-cli-visuals'; diff --git a/typings/resin-device-config.d.ts b/typings/resin-device-config.d.ts new file mode 100644 index 00000000..ea7a11cb --- /dev/null +++ b/typings/resin-device-config.d.ts @@ -0,0 +1 @@ +declare module 'resin-device-config'; diff --git a/typings/resin-device-init.d.ts b/typings/resin-device-init.d.ts new file mode 100644 index 00000000..2ba03252 --- /dev/null +++ b/typings/resin-device-init.d.ts @@ -0,0 +1,60 @@ +declare module 'resin-device-init' { + import * as Promise from 'bluebird'; + import { EventEmitter } from 'events'; + + interface OperationState { + operation: CopyOperation | ReplaceOperation | RunScriptOperation | BurnOperation; + percentage: number; + } + + interface Operation { + command: string; + } + + interface CopyOperation extends Operation { + command: 'copy'; + from: { path: string }; + to: { path: string }; + } + + interface ReplaceOperation extends Operation { + command: 'replace'; + copy: string; + replace: string; + file: { + path: string; + }; + } + + interface RunScriptOperation extends Operation { + command: 'run-script'; + script: string; + arguments?: string[]; + } + + interface BurnOperation extends Operation { + command: 'burn'; + image?: string; + } + + interface BurnProgress { + type: 'write' | 'check'; + percentage: number; + transferred: number; + length: number; + remaining: number; + eta: number; + runtime: number; + delta: number; + speed: number; + } + + interface InitializeEmitter { + on(event: 'stdout', callback: (msg: string) => void): void; + on(event: 'stderr', callback: (msg: string) => void): void; + on(event: 'state', callback: (state: OperationState) => void): void; + on(event: 'burn', callback: (state: BurnProgress) => void): void; + } + + export function initialize(image: string, deviceType: string, config: {}): Promise; +} diff --git a/typings/resin-image-fs.d.ts b/typings/resin-image-fs.d.ts new file mode 100644 index 00000000..00f85d54 --- /dev/null +++ b/typings/resin-image-fs.d.ts @@ -0,0 +1,5 @@ +declare module 'resin-image-fs' { + import Promise = require('bluebird'); + + export function read(options: {}): Promise; +} diff --git a/typings/resin-sdk-preconfigured.d.ts b/typings/resin-sdk-preconfigured.d.ts new file mode 100644 index 00000000..57afac5e --- /dev/null +++ b/typings/resin-sdk-preconfigured.d.ts @@ -0,0 +1,5 @@ +declare module 'resin-sdk-preconfigured' { + import { ResinSDK } from 'resin-sdk'; + let sdk: ResinSDK; + export = sdk; +} diff --git a/typings/resin-sync.d.ts b/typings/resin-sync.d.ts new file mode 100644 index 00000000..cacb7a91 --- /dev/null +++ b/typings/resin-sync.d.ts @@ -0,0 +1,5 @@ +declare module 'resin-sync' { + import { CommandDefinition } from 'capitano'; + + export function capitano(tool: 'resin-cli'): CommandDefinition; +} diff --git a/typings/rindle.d.ts b/typings/rindle.d.ts new file mode 100644 index 00000000..019ad9fd --- /dev/null +++ b/typings/rindle.d.ts @@ -0,0 +1,13 @@ +declare module 'rindle' { + export function extract( + stream: NodeJS.ReadableStream, + callback: (error: Error, data: string) => void, + ): void; + + export function wait( + stream: { + on(event: string, callback: Function): void; + }, + callback: (error: Error, data: string) => void, + ): void; +} diff --git a/typings/update-notifier.d.ts b/typings/update-notifier.d.ts new file mode 100644 index 00000000..9afc8ed5 --- /dev/null +++ b/typings/update-notifier.d.ts @@ -0,0 +1,53 @@ +// Based on the official types at https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/update-notifier/index.d.ts +// but fixed to handle options correctly + +declare module 'update-notifier' { + export = UpdateNotifier; + + function UpdateNotifier(settings?: UpdateNotifier.Settings): UpdateNotifier.UpdateNotifier; + + namespace UpdateNotifier { + class UpdateNotifier { + constructor(settings?: Settings); + + update: UpdateInfo; + check(): void; + checkNpm(): void; + notify(customMessage?: NotifyOptions): void; + } + + interface Settings { + pkg?: Package; + callback?(update?: UpdateInfo): any; + packageName?: string; + packageVersion?: string; + updateCheckInterval?: number; // in milliseconds, default 1000 * 60 * 60 * 24 (1 day) + } + + interface BoxenOptions { + padding: number; + margin: number; + align: string; + borderColor: string; + borderStyle: string; + } + + interface NotifyOptions { + message?: string; + defer?: boolean; + boxenOpts?: BoxenOptions; + } + + interface Package { + name: string; + version: string; + } + + interface UpdateInfo { + latest: string; + current: string; + type: string; + name: string; + } + } +}