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/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/info.coffee b/lib/actions/info.ts similarity index 61% rename from lib/actions/info.coffee rename to lib/actions/info.ts index 004881fd..c55e74b5 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 { Command } from "capitano"; + +export const version: Command = { + 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/settings.coffee b/lib/actions/settings.ts similarity index 58% rename from lib/actions/settings.coffee rename to lib/actions/settings.ts index c5be7025..5981cd30 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 { Command } from "capitano"; - Examples: +export const list: Command = { + 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 88% rename from lib/actions/sync.coffee rename to lib/actions/sync.ts index 9823f35b..d3278ca6 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,6 @@ 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') +export = require('resin-sync').capitano('resin-cli'); 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..cc3be3c1 --- /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..51566efa --- /dev/null +++ b/lib/utils/config.ts @@ -0,0 +1,109 @@ +/* +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'), + pubNubKeys: resin.models.config.getAll().get('pubnub'), + mixpanelToken: resin.models.config.getAll().get('mixpanelToken') + }).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.pubNubKeys, + mixpanel: { + token: results.mixpanelToken + } + }, options); + }); +}; + +export function generateApplicationConfig(application: ResinSdk.Application, options: {}) { + return generateBaseConfig(application, options) + .tap(config => authenticateWithApplicationKey(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 != null) { + return authenticateWithDeviceKey(config, device.uuid, deviceApiKey); + } else { + return 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; + }); +}; + +function authenticateWithApplicationKey(config: any, applicationNameOrId: string | number) { + return resin.models.application.generateApiKey(applicationNameOrId) + .tap((apiKey) => { + config.apiKey = apiKey; + }); +}; + +function authenticateWithDeviceKey(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..067a8e7a --- /dev/null +++ b/lib/utils/helpers.ts @@ -0,0 +1,146 @@ +/* +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 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); + + const presidentExecuteAsync = Promise.promisify(execute); + 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(Promise.promisify(rindle.extract)) + .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; } + 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 Promise.promisify(rindle.wait)(step); +}; + +export function getArchAndDeviceType(applicationName: string): Promise<{ arch: string, device_type: string }> { + return Promise.join( + getApplication(applicationName), + resin.models.config.getDeviceTypes(), + function (app, deviceTypes) { + let config = _.find(deviceTypes, { 'slug': app.device_type }); + + if (config == null) { + throw new Error('Could not read application information!'); + } + + return { device_type: app.device_type, arch: config.arch }; + } + ); +}; + +function getApplication(applicationName: string) { + let match; + + // 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(applicationName)) { + 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..6a0aaf86 --- /dev/null +++ b/lib/utils/logger.ts @@ -0,0 +1,66 @@ +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') + }; + + _.mapKeys(this.streams, function(stream, key) { + if (key !== 'debug') { + return stream.pipe(process.stdout); + } else { + if (process.env.DEBUG != null) { return 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/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..e7ed9f62 --- /dev/null +++ b/lib/utils/patterns.ts @@ -0,0 +1,228 @@ +/* +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: {}) { + 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) => + resin.auth.logout().then(function() { + 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) => { + let appOptions: { name: string, value: string | null }[]; + appOptions = _.map(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 != null) 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: ResinSdk.Device) => device.is_online) + .then((onlineDevices: ResinSdk.Device[]) => { + if (_.isEmpty(onlineDevices)) { + throw new Error('You don\'t have any devices online'); + } + + let defaultUuid = Array.from(_.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 51% rename from lib/utils/plugins.coffee rename to lib/utils/plugins.ts index f7addd9b..9cb3b950 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) +exports.register = (regex: RegExp) => { + 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..8459f94f --- /dev/null +++ b/lib/utils/streams.ts @@ -0,0 +1,20 @@ +import { ReadStream } from 'fs'; + +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) { + return stream + .on('error', reject) + .on('end', resolve) + .pipe(fileWriteStream); + }).then(() => new Promise(function(resolve, reject) { + fs.createReadStream(bufferFile) + .on('open', function(this: ReadStream) { + resolve(this); + }).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..f26d80bf --- /dev/null +++ b/lib/utils/update.ts @@ -0,0 +1,51 @@ +/* +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 hasAvailableUpdate() { + return notifier != null; +} + +export function notify() { + 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'); + } +}; diff --git a/lib/utils/validation.ts b/lib/utils/validation.ts index a16db94e..3f70c122 100644 --- a/lib/utils/validation.ts +++ b/lib/utils/validation.ts @@ -16,7 +16,7 @@ limitations under the License. import validEmail = require('@resin.io/valid-email'); -exports.validateEmail = function(input: string) { +export function validateEmail(input: string) { if (!validEmail(input)) { return 'Email is not valid'; } @@ -24,7 +24,7 @@ exports.validateEmail = function(input: string) { return true; }; -exports.validatePassword = function(input: string) { +export function validatePassword(input: string) { if (input.length < 8) { return 'Password should be 8 characters long'; } @@ -32,7 +32,7 @@ exports.validatePassword = function(input: string) { return true; }; -exports.validateApplicationName = function(input: string) { +export function validateApplicationName(input: string) { if (input.length < 4) { return 'The application name should be at least 4 characters'; } diff --git a/package.json b/package.json index e76b2770..65258af4 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,12 @@ }, "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", @@ -71,7 +75,7 @@ "publish-release": "^1.3.3", "require-npm4-to-publish": "^1.0.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", @@ -143,4 +147,4 @@ "optionalDependencies": { "removedrive": "^1.0.0" } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index bb840ae9..422de22a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,17 @@ "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", diff --git a/typings/capitano.d.ts b/typings/capitano.d.ts new file mode 100644 index 00000000..25e33697 --- /dev/null +++ b/typings/capitano.d.ts @@ -0,0 +1,36 @@ +declare module 'capitano' { + export function parse(argv: string[]): Cli; + + export interface Cli { + command: string; + options: {}; + global: {}; + } + + export interface CommandOption { + signature: string; + description: string; + parameter?: string; + boolean?: boolean; + alias?: string | string[]; + } + + export interface Command

{ + signature: string; + description: string; + help: string; + options?: CommandOption[], + permission?: 'user', + action(params: P, options: O, done: () => void): void; + } + + export interface BuiltCommand { + signature: {} + } + + export function command(command: Command): void; + + export const state: { + getMatchCommand: (signature: string, callback: (e: Error, cmd: BuiltCommand) => 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-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..50f434ca --- /dev/null +++ b/typings/resin-device-init.d.ts @@ -0,0 +1,59 @@ +declare module 'resin-device-init' { + 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..82e14041 --- /dev/null +++ b/typings/resin-sdk-preconfigured.d.ts @@ -0,0 +1,5 @@ +declare module 'resin-sdk-preconfiguredasd' { + import { ResinSDK } from 'resin-sdk'; + let sdk: ResinSDK; + export = sdk; +} diff --git a/typings/rindle.d.ts b/typings/rindle.d.ts new file mode 100644 index 00000000..b38d0317 --- /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; + } + } +}