From 4b511c47f0654b85fc54812ee5e2b8696119b0df Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Tue, 19 Dec 2017 18:00:31 +0100 Subject: [PATCH 1/7] Start on some easy TS conversion --- lib/config.ts | 2 +- lib/utils/messages.coffee | 22 ------------ lib/utils/messages.ts | 22 ++++++++++++ .../{validation.coffee => validation.ts} | 36 +++++++++++-------- tsconfig.json | 1 + typings/@resin-valid-email.d.ts | 1 + typings/resin-cli-errors.d.ts | 1 + typings/resin-cli-form.d.ts | 1 + typings/resin-cli-visuals.d.ts | 1 + 9 files changed, 49 insertions(+), 38 deletions(-) delete mode 100644 lib/utils/messages.coffee create mode 100644 lib/utils/messages.ts rename lib/utils/{validation.coffee => validation.ts} (52%) create mode 100644 typings/@resin-valid-email.d.ts create mode 100644 typings/resin-cli-errors.d.ts create mode 100644 typings/resin-cli-form.d.ts create mode 100644 typings/resin-cli-visuals.d.ts diff --git a/lib/config.ts b/lib/config.ts index 83351520..e2ae8f2d 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1 +1 @@ -exports.sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239' +exports.sentryDsn = 'https://56d2a46124614b01b0f4086897e96110:6e175465accc41b595a96947155f61fb@sentry.io/149239'; 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/validation.coffee b/lib/utils/validation.ts similarity index 52% rename from lib/utils/validation.coffee rename to lib/utils/validation.ts index 2901a982..a16db94e 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' +exports.validateEmail = function(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' +exports.validatePassword = function(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' +exports.validateApplicationName = function(input: string) { + if (input.length < 4) { + return 'The application name should be at least 4 characters'; + } - return true + return true; +}; diff --git a/tsconfig.json b/tsconfig.json index fc436833..bb840ae9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "sourceMap": true }, "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/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'; From ffffd447f28a8051519e08055b0686a253e69f34 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 20 Dec 2017 22:46:01 +0100 Subject: [PATCH 2/7] Convert most of utils to TypeScript Change-Type: patch --- lib/actions/build.coffee | 2 +- lib/actions/deploy.coffee | 2 +- lib/actions/{info.coffee => info.ts} | 28 ++- lib/actions/{settings.coffee => settings.ts} | 34 +-- lib/actions/{sync.coffee => sync.ts} | 6 +- lib/errors.coffee | 38 ---- lib/errors.ts | 39 ++++ lib/events.coffee | 34 --- lib/events.ts | 42 ++++ lib/utils/config.coffee | 97 -------- lib/utils/config.ts | 109 +++++++++ lib/utils/helpers.coffee | 135 ----------- lib/utils/helpers.ts | 146 ++++++++++++ lib/utils/logger.coffee | 46 ---- lib/utils/logger.ts | 66 ++++++ lib/utils/patterns.coffee | 181 --------------- lib/utils/patterns.ts | 228 +++++++++++++++++++ lib/utils/{plugins.coffee => plugins.ts} | 32 +-- lib/utils/streams.coffee | 17 -- lib/utils/streams.ts | 20 ++ lib/utils/update.coffee | 40 ---- lib/utils/update.ts | 51 +++++ lib/utils/validation.ts | 6 +- package.json | 12 +- tsconfig.json | 12 +- typings/capitano.d.ts | 36 +++ typings/mixpanel.d.ts | 1 + typings/nplugm.d.ts | 4 + typings/package.json.d.ts | 4 + typings/president.d.ts | 3 + typings/resin-device-config.d.ts | 1 + typings/resin-device-init.d.ts | 59 +++++ typings/resin-image-fs.d.ts | 5 + typings/resin-sdk-preconfigured.d.ts | 5 + typings/rindle.d.ts | 13 ++ typings/update-notifier.d.ts | 53 +++++ 36 files changed, 965 insertions(+), 642 deletions(-) rename lib/actions/{info.coffee => info.ts} (61%) rename lib/actions/{settings.coffee => settings.ts} (58%) rename lib/actions/{sync.coffee => sync.ts} (88%) delete mode 100644 lib/errors.coffee create mode 100644 lib/errors.ts delete mode 100644 lib/events.coffee create mode 100644 lib/events.ts delete mode 100644 lib/utils/config.coffee create mode 100644 lib/utils/config.ts delete mode 100644 lib/utils/helpers.coffee create mode 100644 lib/utils/helpers.ts delete mode 100644 lib/utils/logger.coffee create mode 100644 lib/utils/logger.ts delete mode 100644 lib/utils/patterns.coffee create mode 100644 lib/utils/patterns.ts rename lib/utils/{plugins.coffee => plugins.ts} (51%) delete mode 100644 lib/utils/streams.coffee create mode 100644 lib/utils/streams.ts delete mode 100644 lib/utils/update.coffee create mode 100644 lib/utils/update.ts create mode 100644 typings/capitano.d.ts create mode 100644 typings/mixpanel.d.ts create mode 100644 typings/nplugm.d.ts create mode 100644 typings/package.json.d.ts create mode 100644 typings/president.d.ts create mode 100644 typings/resin-device-config.d.ts create mode 100644 typings/resin-device-init.d.ts create mode 100644 typings/resin-image-fs.d.ts create mode 100644 typings/resin-sdk-preconfigured.d.ts create mode 100644 typings/rindle.d.ts create mode 100644 typings/update-notifier.d.ts 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; + } + } +} From f25442c036e4805909992b3726bdc1519ca723ab Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 21 Dec 2017 18:40:13 +0100 Subject: [PATCH 3/7] Move documentation generation to TypeScript Change-Type: patch --- capitanodoc.ts | 114 +++++++++++++++++++++++++++++ doc/cli.markdown | 18 ++--- extras/capitanodoc/doc-types.d.ts | 14 ++++ extras/capitanodoc/index.coffee | 46 ------------ extras/capitanodoc/index.ts | 33 +++++++++ extras/capitanodoc/markdown.coffee | 66 ----------------- extras/capitanodoc/markdown.ts | 90 +++++++++++++++++++++++ extras/capitanodoc/utils.coffee | 26 ------- extras/capitanodoc/utils.ts | 35 +++++++++ extras/tsconfig.json | 17 +++++ lib/actions/info.ts | 4 +- lib/actions/settings.ts | 4 +- package.json | 4 +- typings/capitano.d.ts | 31 ++++++-- 14 files changed, 342 insertions(+), 160 deletions(-) create mode 100644 capitanodoc.ts create mode 100644 extras/capitanodoc/doc-types.d.ts delete mode 100644 extras/capitanodoc/index.coffee create mode 100644 extras/capitanodoc/index.ts delete mode 100644 extras/capitanodoc/markdown.coffee create mode 100644 extras/capitanodoc/markdown.ts delete mode 100644 extras/capitanodoc/utils.coffee create mode 100644 extras/capitanodoc/utils.ts create mode 100644 extras/tsconfig.json 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..4346a41a --- /dev/null +++ b/extras/capitanodoc/index.ts @@ -0,0 +1,33 @@ +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) { + const actions: any = require(path.join(process.cwd(), file)); + + if (actions.signature != null) { + category.commands.push(_.omit(actions, 'action')); + } else { + for (let actionName in 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..6a272d1b --- /dev/null +++ b/extras/capitanodoc/markdown.ts @@ -0,0 +1,90 @@ +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)} + +${command.help}\n\ +`; + + if (!_.isEmpty(command.options)) { + result += '\n### Options'; + + for (let option of command.options!) { + result += `\ +\n\n#### ${utils.parseSignature(option)} + +${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} + +${doc.introduction} + +${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..bda4c343 --- /dev/null +++ b/extras/capitanodoc/utils.ts @@ -0,0 +1,35 @@ +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 (!_.isEmpty(option.alias)) { + if (_.isString(option.alias)) { + result += `, ${getOptionSignature(option.alias)}`; + } else { + for (let alias of option.alias!) { + result += `, ${getOptionSignature(alias)}`; + } + } + } + + if (option.parameter != null) { + 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/lib/actions/info.ts b/lib/actions/info.ts index c55e74b5..f9ed173f 100644 --- a/lib/actions/info.ts +++ b/lib/actions/info.ts @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Command } from "capitano"; +import { CommandDefinition } from "capitano"; -export const version: Command = { +export const version: CommandDefinition = { signature: 'version', description: 'output the version number', help: `\ diff --git a/lib/actions/settings.ts b/lib/actions/settings.ts index 5981cd30..1832a06c 100644 --- a/lib/actions/settings.ts +++ b/lib/actions/settings.ts @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Command } from "capitano"; +import { CommandDefinition } from "capitano"; -export const list: Command = { +export const list: CommandDefinition = { signature: 'settings', description: 'print current settings', help: `\ diff --git a/package.json b/package.json index 65258af4..7315b653 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,13 @@ "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": "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", "prepublish": "require-npm4-to-publish", diff --git a/typings/capitano.d.ts b/typings/capitano.d.ts index 25e33697..e1c63547 100644 --- a/typings/capitano.d.ts +++ b/typings/capitano.d.ts @@ -7,7 +7,7 @@ declare module 'capitano' { global: {}; } - export interface CommandOption { + export interface OptionDefinition { signature: string; description: string; parameter?: string; @@ -15,22 +15,39 @@ declare module 'capitano' { alias?: string | string[]; } - export interface Command

{ + export interface CommandDefinition

{ signature: string; description: string; help: string; - options?: CommandOption[], + options?: OptionDefinition[], permission?: 'user', action(params: P, options: O, done: () => void): void; } - export interface BuiltCommand { - signature: {} + export interface Command { + signature: Signature; + options: Option[]; + isWildcard(): boolean; } - export function command(command: Command): void; + 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: BuiltCommand) => void) => void + getMatchCommand: (signature: string, callback: (e: Error, cmd: Command) => void) => void }; } From 6daed83d8888e852eba4c9b6087148ce2061fc2c Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 4 Jan 2018 14:07:55 +0000 Subject: [PATCH 4/7] Lint TypeScript and CoffeeScript with resin-lint Change-Type: patch --- automation/custom-types.d.ts | 14 +++---- automation/deploy-bin.ts | 7 ++-- automation/tsconfig.json | 3 +- extras/capitanodoc/index.ts | 5 ++- extras/capitanodoc/markdown.ts | 8 ++-- extras/capitanodoc/utils.ts | 4 +- gulpfile.coffee | 12 +----- lib/actions/config.coffee | 1 - lib/actions/device.coffee | 1 - lib/actions/info.ts | 4 +- lib/actions/logs.coffee | 1 - lib/actions/preload.coffee | 4 +- lib/actions/settings.ts | 4 +- lib/actions/sync.ts | 3 +- lib/app.coffee | 1 - lib/config.ts | 2 +- lib/events.ts | 12 +++--- lib/utils/config.ts | 22 +++++------ lib/utils/helpers.ts | 34 ++++++++--------- lib/utils/logger.ts | 2 +- lib/utils/patterns.ts | 68 +++++++++++++++++----------------- lib/utils/plugins.ts | 6 +-- lib/utils/streams.ts | 2 +- lib/utils/update.ts | 4 +- lib/utils/validation.ts | 6 +-- package.json | 6 +-- typings/capitano.d.ts | 4 +- typings/resin-device-init.d.ts | 6 +-- typings/resin-sync.d.ts | 5 +++ typings/rindle.d.ts | 4 +- 30 files changed, 123 insertions(+), 132 deletions(-) create mode 100644 typings/resin-sync.d.ts 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/extras/capitanodoc/index.ts b/extras/capitanodoc/index.ts index 4346a41a..875ee344 100644 --- a/extras/capitanodoc/index.ts +++ b/extras/capitanodoc/index.ts @@ -15,12 +15,13 @@ for (let commandCategory of capitanodoc.categories) { 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 != null) { + if (actions.signature) { category.commands.push(_.omit(actions, 'action')); } else { - for (let actionName in actions) { + for (let actionName of Object.keys(actions)) { const actionCommand = actions[actionName]; category.commands.push(_.omit(actionCommand, 'action')); } diff --git a/extras/capitanodoc/markdown.ts b/extras/capitanodoc/markdown.ts index 6a272d1b..0bd1279f 100644 --- a/extras/capitanodoc/markdown.ts +++ b/extras/capitanodoc/markdown.ts @@ -25,7 +25,7 @@ ${option.description}\ } return result; -}; +} export function renderCategory(category: Category) { let result = `\ @@ -37,7 +37,7 @@ export function renderCategory(category: Category) { } return result; -}; +} function getAnchor(command: Command) { return '#' + command.signature @@ -71,7 +71,7 @@ export function renderToc(categories: Category[]) { } return result; -}; +} export function render(doc: Document) { let result = `\ @@ -87,4 +87,4 @@ ${renderToc(doc.categories)}\ } return result; -}; +} diff --git a/extras/capitanodoc/utils.ts b/extras/capitanodoc/utils.ts index bda4c343..d05a15a6 100644 --- a/extras/capitanodoc/utils.ts +++ b/extras/capitanodoc/utils.ts @@ -8,7 +8,7 @@ export function getOptionPrefix(signature: string) { } else { return '-'; } -}; +} export function getOptionSignature(signature: string) { return `${getOptionPrefix(signature)}${signature}`; @@ -32,4 +32,4 @@ export function parseSignature(option: OptionDefinition) { } return ent.encode(result); -}; +} 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/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/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.ts b/lib/actions/info.ts index f9ed173f..52072191 100644 --- a/lib/actions/info.ts +++ b/lib/actions/info.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CommandDefinition } from "capitano"; +import { CommandDefinition } from 'capitano'; export const version: CommandDefinition = { signature: 'version', @@ -26,5 +26,5 @@ Display the Resin CLI version.\ 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.ts b/lib/actions/settings.ts index 1832a06c..ae76c00d 100644 --- a/lib/actions/settings.ts +++ b/lib/actions/settings.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { CommandDefinition } from "capitano"; +import { CommandDefinition } from 'capitano'; export const list: CommandDefinition = { signature: 'settings', @@ -34,5 +34,5 @@ Examples: .then(prettyjson.render) .then(console.log) .nodeify(done); - } + }, }; diff --git a/lib/actions/sync.ts b/lib/actions/sync.ts index d3278ca6..dcac6e4f 100644 --- a/lib/actions/sync.ts +++ b/lib/actions/sync.ts @@ -14,4 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -export = 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 e2ae8f2d..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/events.ts b/lib/events.ts index cc3be3c1..c5c539fd 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -15,14 +15,14 @@ export function trackCommand(capitanoCli: Capitano.Cli) { return Promise.props({ resinUrl: resin.settings.get('resinUrl'), username: resin.auth.whoami().catchReturn(undefined), - mixpanel: getMixpanel() + mixpanel: getMixpanel(), }).then(({ username, resinUrl, mixpanel }) => { return getMatchCommandAsync(capitanoCli.command).then((command) => { Raven.mergeContext({ user: { id: username, - username - } + username, + }, }); return mixpanel.track(`[CLI] ${command.signature.toString()}`, { @@ -33,10 +33,10 @@ export function trackCommand(capitanoCli: Capitano.Cli) { arch: process.arch, resinUrl, platform: process.platform, - command: capitanoCli + command: capitanoCli, }); - }) + }); }) .timeout(100) .catchReturn(undefined); -}; +} diff --git a/lib/utils/config.ts b/lib/utils/config.ts index 51566efa..a297e01c 100644 --- a/lib/utils/config.ts +++ b/lib/utils/config.ts @@ -37,27 +37,27 @@ export function generateBaseConfig(application: ResinSdk.Application, options: { registryUrl: resin.settings.get('registryUrl'), deltaUrl: resin.settings.get('deltaUrl'), pubNubKeys: resin.models.config.getAll().get('pubnub'), - mixpanelToken: resin.models.config.getAll().get('mixpanelToken') + mixpanelToken: resin.models.config.getAll().get('mixpanelToken'), }).then((results) => { return deviceConfig.generate({ application, user: { id: results.userId, - username: results.username + username: results.username, }, endpoints: { api: results.apiUrl, vpn: results.vpnUrl, registry: results.registryUrl, - delta: results.deltaUrl + delta: results.deltaUrl, }, pubnub: results.pubNubKeys, mixpanel: { - token: results.mixpanelToken - } + token: results.mixpanelToken, + }, }, options); }); -}; +} export function generateApplicationConfig(application: ResinSdk.Application, options: {}) { return generateBaseConfig(application, options) @@ -67,7 +67,7 @@ export function generateApplicationConfig(application: ResinSdk.Application, opt export function generateDeviceConfig( device: ResinSdk.Device & { application_name: string }, deviceApiKey: string | null, - options: {} + options: {}, ) { return resin.models.application.get(device.application_name) .then(application => { @@ -91,19 +91,19 @@ export function generateDeviceConfig( 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) + return customDeviceApiKey || resin.models.device.generateDeviceKey(uuid); }).tap((deviceApiKey) => { config.deviceApiKey = deviceApiKey; }); -}; +} diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 067a8e7a..e17e912e 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -24,19 +24,19 @@ import visuals = require('resin-cli-visuals'); import ResinSdk = require('resin-sdk'); import { execute } from 'president'; -import { InitializeEmitter, OperationState } from "resin-device-init"; +import { InitializeEmitter, OperationState } from 'resin-device-init'; const resin = ResinSdk.fromSharedOptions(); export function getGroupDefaults( - group: { options: { name: string, default?: string }[] } + 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') + '%'; @@ -52,7 +52,7 @@ export function stateToString(state: OperationState) { default: throw new Error(`Unsupported operation: ${state.operation.command}`); } -}; +} export function sudo(command: string[]) { if (os.platform() !== 'win32') { @@ -63,7 +63,7 @@ export function sudo(command: string[]) { const presidentExecuteAsync = Promise.promisify(execute); return presidentExecuteAsync(command); -}; +} export function getManifest(image: string, deviceType: string): Promise { // Attempt to read manifest from the first @@ -72,13 +72,13 @@ export function getManifest(image: string, deviceType: string): Promise resin.models.device.getManifestBySlug(deviceType)); -}; +} export function osProgressHandler(step: InitializeEmitter) { step.on('stdout', process.stdout.write.bind(process.stdout)); @@ -91,29 +91,29 @@ export function osProgressHandler(step: InitializeEmitter) { const progressBars = { write: new visuals.Progress('Writing Device OS'), - check: new visuals.Progress('Validating 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 }); + 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; @@ -125,7 +125,7 @@ function getApplication(applicationName: string) { } return resin.models.application.get(applicationName); -}; +} // A function to reliably execute a command // in all supported operating systems, including @@ -135,12 +135,12 @@ export function getSubShellCommand(command: string) { if (os.platform() === 'win32') { return { program: 'cmd.exe', - args: [ '/s', '/c', command ] + args: [ '/s', '/c', command ], }; } else { return { program: '/bin/sh', - args: [ '-c', command ] + args: [ '-c', command ], }; } -}; +} diff --git a/lib/utils/logger.ts b/lib/utils/logger.ts index 6a0aaf86..a8ba0f56 100644 --- a/lib/utils/logger.ts +++ b/lib/utils/logger.ts @@ -30,7 +30,7 @@ export class Logger { debug: logger.createLogStream('debug'), success: logger.createLogStream('success'), warn: logger.createLogStream('warn'), - error: logger.createLogStream('error') + error: logger.createLogStream('error'), }; _.mapKeys(this.streams, function(stream, key) { diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index e7ed9f62..37ccef15 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -30,11 +30,11 @@ export function authenticate(options: {}) { message: 'Email:', name: 'email', type: 'input', - validate: validation.validateEmail + validate: validation.validateEmail, }, { message: 'Password:', name: 'password', - type: 'password' + type: 'password', }], { override: options }) .then(resin.auth.login) .then(resin.auth.twoFactor.isPassed) @@ -45,16 +45,14 @@ export function authenticate(options: {}) { 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; - }) - ); - }) -}; + .catch((error: any) => 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({ @@ -63,18 +61,18 @@ export function askLoginType() { type: 'list', choices: [{ name: 'Web authorization (recommended)', - value: 'web' + value: 'web', }, { name: 'Credentials', - value: 'credentials' + value: 'credentials', }, { name: 'Authentication token', - value: 'token' + value: 'token', }, { name: 'I don\'t have a Resin account!', - value: 'register' - }] - }) + value: 'register', + }], + }); } export function selectDeviceType() { @@ -83,8 +81,8 @@ export function selectDeviceType() { return form.ask({ message: 'Device Type', type: 'list', - choices: deviceTypes - }) + choices: deviceTypes, + }); }); } @@ -98,13 +96,13 @@ export function confirm(yesOption: string, message: string, yesMessage: string) return form.ask({ message, type: 'confirm', - default: false + default: false, }); }).then(function(confirmed) { if (!confirmed) { throw new Error('Aborted'); } - }) + }); } export function selectApplication(filter: (app: ResinSdk.Application) => boolean) { @@ -123,10 +121,10 @@ export function selectApplication(filter: (app: ResinSdk.Application) => boolean choices: _.map(applications, application => ({ name: `${application.app_name} (${application.device_type})`, - value: application.app_name - }) + value: application.app_name, + }), )}); - }) + }); } export function selectOrCreateApplication() { @@ -137,18 +135,18 @@ export function selectOrCreateApplication() { let appOptions: { name: string, value: string | null }[]; appOptions = _.map(applications, application => ({ name: `${application.app_name} (${application.device_type})`, - value: application.app_name + value: application.app_name, })); appOptions.unshift({ name: 'Create a new application', - value: null + value: null, }); return form.ask({ message: 'Select an application', type: 'list', - choices: appOptions + choices: appOptions, }); }); }).then((application) => { @@ -157,9 +155,9 @@ export function selectOrCreateApplication() { return form.ask({ message: 'Choose a Name for your new application', type: 'input', - validate: validation.validateApplicationName + validate: validation.validateApplicationName, }); - }) + }); } export function awaitDevice(uuid: string) { @@ -182,7 +180,7 @@ export function awaitDevice(uuid: string) { return Promise.delay(3000).then(poll); } }); - } + }; console.info(`Waiting for ${deviceName} to connect to resin...`); return poll().return(uuid); @@ -207,8 +205,8 @@ export function inferOrSelectDevice(preferredUuid: string) { default: defaultUuid, choices: _.map(onlineDevices, device => ({ name: `${device.name || 'Untitled'} (${device.uuid.slice(0, 7)})`, - value: device.uuid - }) + value: device.uuid, + }), )}); }); } @@ -216,7 +214,7 @@ export function inferOrSelectDevice(preferredUuid: string) { 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) { @@ -225,4 +223,4 @@ export function expectedError(message: string | Error) { printErrorMessage(message); process.exit(1); -}; +} diff --git a/lib/utils/plugins.ts b/lib/utils/plugins.ts index 9cb3b950..1896c7b9 100644 --- a/lib/utils/plugins.ts +++ b/lib/utils/plugins.ts @@ -19,7 +19,7 @@ import _ = require('lodash'); import capitano = require('capitano'); import patterns = require('./patterns'); -exports.register = (regex: RegExp) => { +export function register(regex: RegExp): Promise { return nplugm.list(regex).map(async function(plugin: any) { const command = await import(plugin); command.plugin = true; @@ -28,6 +28,6 @@ exports.register = (regex: RegExp) => { } return _.each(command, capitano.command); }).catch((error: Error) => { - return patterns.printErrorMessage(error.message) - }) + return patterns.printErrorMessage(error.message); + }); } diff --git a/lib/utils/streams.ts b/lib/utils/streams.ts index 8459f94f..7dd9cdd8 100644 --- a/lib/utils/streams.ts +++ b/lib/utils/streams.ts @@ -17,4 +17,4 @@ export async function buffer(stream: NodeJS.ReadableStream, bufferFile: string) resolve(this); }).on('error', reject); })); -}; +} diff --git a/lib/utils/update.ts b/lib/utils/update.ts index f26d80bf..e6ed56b4 100644 --- a/lib/utils/update.ts +++ b/lib/utils/update.ts @@ -30,7 +30,7 @@ let notifier: UpdateNotifier.UpdateNotifier; if (!isRoot()) { notifier = UpdateNotifier({ pkg: packageJSON, - updateCheckInterval: resinUpdateInterval + updateCheckInterval: resinUpdateInterval, }); } @@ -48,4 +48,4 @@ export function notify() { 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 3f70c122..010397e9 100644 --- a/lib/utils/validation.ts +++ b/lib/utils/validation.ts @@ -22,7 +22,7 @@ export function validateEmail(input: string) { } return true; -}; +} export function validatePassword(input: string) { if (input.length < 8) { @@ -30,7 +30,7 @@ export function validatePassword(input: string) { } return true; -}; +} export function validateApplicationName(input: string) { if (input.length < 4) { @@ -38,4 +38,4 @@ export function validateApplicationName(input: string) { } return true; -}; +} diff --git a/package.json b/package.json index 7315b653..00ebe6c6 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "scripts": { "prebuild": "rimraf build/ build-bin/ build-zip/", "build": "npm run build:src && npm run build:bin", - "build:src": "gulp build && tsc && npm run build: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", @@ -39,7 +39,7 @@ "test": "gulp test", "ci": "npm run test && catch-uncommitted", "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" }, @@ -66,7 +66,6 @@ "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", @@ -74,6 +73,7 @@ "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.4.0" }, diff --git a/typings/capitano.d.ts b/typings/capitano.d.ts index e1c63547..1958a6db 100644 --- a/typings/capitano.d.ts +++ b/typings/capitano.d.ts @@ -19,8 +19,8 @@ declare module 'capitano' { signature: string; description: string; help: string; - options?: OptionDefinition[], - permission?: 'user', + options?: OptionDefinition[]; + permission?: 'user'; action(params: P, options: O, done: () => void): void; } diff --git a/typings/resin-device-init.d.ts b/typings/resin-device-init.d.ts index 50f434ca..1fdae108 100644 --- a/typings/resin-device-init.d.ts +++ b/typings/resin-device-init.d.ts @@ -1,5 +1,5 @@ declare module 'resin-device-init' { - import { EventEmitter } from "events"; + import { EventEmitter } from 'events'; interface OperationState { operation: CopyOperation | ReplaceOperation | RunScriptOperation | BurnOperation; @@ -22,7 +22,7 @@ declare module 'resin-device-init' { replace: string; file: { path: string; - } + }; } interface RunScriptOperation extends Operation { @@ -55,5 +55,5 @@ declare module 'resin-device-init' { on(event: 'burn', callback: (state: BurnProgress) => void): void; } - export function initialize(image: string, deviceType: string, config: {}): Promise + export function initialize(image: string, deviceType: string, config: {}): Promise; } 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 index b38d0317..019ad9fd 100644 --- a/typings/rindle.d.ts +++ b/typings/rindle.d.ts @@ -1,13 +1,13 @@ declare module 'rindle' { export function extract( stream: NodeJS.ReadableStream, - callback: (error: Error, data: string) => void + callback: (error: Error, data: string) => void, ): void; export function wait( stream: { on(event: string, callback: Function): void; }, - callback: (error: Error, data: string) => void + callback: (error: Error, data: string) => void, ): void; } From 6ab60d0ccd172bf853d0fe2cf1cdc2432ee00640 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 4 Jan 2018 15:19:44 +0000 Subject: [PATCH 5/7] Avoid awkward multiline strings in doc generation code --- extras/capitanodoc/markdown.ts | 36 +++++++--------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/extras/capitanodoc/markdown.ts b/extras/capitanodoc/markdown.ts index 0bd1279f..2a2d7510 100644 --- a/extras/capitanodoc/markdown.ts +++ b/extras/capitanodoc/markdown.ts @@ -4,21 +4,13 @@ import * as utils from './utils'; import { Document, Category, Command } from './doc-types'; export function renderCommand(command: Command) { - let result = `\ -## ${ent.encode(command.signature)} - -${command.help}\n\ -`; + 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)} - -${option.description}\ -`; + result += `\n\n#### ${utils.parseSignature(option)}\n\n${option.description}`; } result += '\n'; @@ -28,9 +20,7 @@ ${option.description}\ } export function renderCategory(category: Category) { - let result = `\ -# ${category.title}\n\ -`; + let result = `# ${category.title}\n`; for (let command of category.commands) { result += `\n${renderCommand(command)}`; @@ -53,20 +43,14 @@ function getAnchor(command: Command) { } export function renderToc(categories: Category[]) { - let result = `\ -# Table of contents\n\ -`; + let result = `# Table of contents\n`; for (let category of categories) { - result += `\ -\n- ${category.title}\n\n\ -`; + result += `\n- ${category.title}\n\n`; for (let command of category.commands) { - result += `\ -\t- [${ent.encode(command.signature)}](${getAnchor(command)})\n\ -`; + result += `\t- [${ent.encode(command.signature)}](${getAnchor(command)})\n`; } } @@ -74,13 +58,7 @@ export function renderToc(categories: Category[]) { } export function render(doc: Document) { - let result = `\ -# ${doc.title} - -${doc.introduction} - -${renderToc(doc.categories)}\ -`; + let result = `# ${doc.title}\n\n${doc.introduction}\n\n${renderToc(doc.categories)}`; for (let category of doc.categories) { result += `\n${renderCategory(category)}`; From 612012aff8218689d06ccd55f4383981fdb357a9 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 4 Jan 2018 16:17:43 +0000 Subject: [PATCH 6/7] Lots of small TypeScript tweaks & clarifications from review --- extras/capitanodoc/utils.ts | 14 +++++----- lib/utils/config.ts | 19 +++++++------- lib/utils/helpers.ts | 28 +++++++++++--------- lib/utils/logger.ts | 8 +++--- lib/utils/patterns.ts | 38 +++++++++++++++++----------- lib/utils/streams.ts | 13 +++++----- lib/utils/update.ts | 8 ++---- typings/resin-device-init.d.ts | 1 + typings/resin-sdk-preconfigured.d.ts | 2 +- 9 files changed, 67 insertions(+), 64 deletions(-) diff --git a/extras/capitanodoc/utils.ts b/extras/capitanodoc/utils.ts index d05a15a6..53725f9c 100644 --- a/extras/capitanodoc/utils.ts +++ b/extras/capitanodoc/utils.ts @@ -17,17 +17,15 @@ export function getOptionSignature(signature: string) { export function parseSignature(option: OptionDefinition) { let result = getOptionSignature(option.signature); - if (!_.isEmpty(option.alias)) { - if (_.isString(option.alias)) { - result += `, ${getOptionSignature(option.alias)}`; - } else { - for (let alias of option.alias!) { - result += `, ${getOptionSignature(alias)}`; - } + 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 != null) { + if (option.parameter) { result += ` <${option.parameter}>`; } diff --git a/lib/utils/config.ts b/lib/utils/config.ts index a297e01c..5a797197 100644 --- a/lib/utils/config.ts +++ b/lib/utils/config.ts @@ -36,8 +36,7 @@ export function generateBaseConfig(application: ResinSdk.Application, options: { 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'), + apiConfig: resin.models.config.getAll(), }).then((results) => { return deviceConfig.generate({ application, @@ -51,9 +50,9 @@ export function generateBaseConfig(application: ResinSdk.Application, options: { registry: results.registryUrl, delta: results.deltaUrl, }, - pubnub: results.pubNubKeys, + pubnub: results.apiConfig.pubnub, mixpanel: { - token: results.mixpanelToken, + token: results.apiConfig.mixpanelToken, }, }, options); }); @@ -61,7 +60,7 @@ export function generateBaseConfig(application: ResinSdk.Application, options: { export function generateApplicationConfig(application: ResinSdk.Application, options: {}) { return generateBaseConfig(application, options) - .tap(config => authenticateWithApplicationKey(config, application.id)); + .tap(config => addApplicationKey(config, application.id)); } export function generateDeviceConfig( @@ -76,10 +75,10 @@ export function generateDeviceConfig( // 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); + if (deviceApiKey) { + return addDeviceKey(config, device.uuid, deviceApiKey); } else { - return authenticateWithApplicationKey(config, application.id); + return addApplicationKey(config, application.id); } }); }).then((config) => { @@ -93,14 +92,14 @@ export function generateDeviceConfig( }); } -function authenticateWithApplicationKey(config: any, applicationNameOrId: string | number) { +function addApplicationKey(config: any, applicationNameOrId: string | number) { return resin.models.application.generateApiKey(applicationNameOrId) .tap((apiKey) => { config.apiKey = apiKey; }); } -function authenticateWithDeviceKey(config: any, uuid: string, customDeviceApiKey: string) { +function addDeviceKey(config: any, uuid: string, customDeviceApiKey: string) { return Promise.try(() => { return customDeviceApiKey || resin.models.device.generateDeviceKey(uuid); }).tap((deviceApiKey) => { diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index e17e912e..b0bc8e3c 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -26,6 +26,10 @@ 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( @@ -39,8 +43,8 @@ export function getGroupDefaults( } export function stateToString(state: OperationState) { - const percentage = _.padStart(`${state.percentage}`, 3, '0') + '%'; - const result = `${chalk.blue(percentage)} ${chalk.cyan(state.operation.command)}`; + const percentage = _.padStart(`${state.percentage}`, 3, '0'); + const result = `${chalk.blue(percentage + '%')} ${chalk.cyan(state.operation.command)}`; switch (state.operation.command) { case 'copy': @@ -61,7 +65,6 @@ export function sudo(command: string[]) { command = _.union(_.take(process.argv, 2), command); - const presidentExecuteAsync = Promise.promisify(execute); return presidentExecuteAsync(command); } @@ -75,7 +78,8 @@ export function getManifest(image: string, deviceType: string): Promise resin.models.device.getManifestBySlug(deviceType)); } @@ -86,7 +90,7 @@ export function osProgressHandler(step: InitializeEmitter) { step.on('state', function(state) { if (state.operation.command === 'burn') { return; } - return console.log(exports.stateToString(state)); + console.log(exports.stateToString(state)); }); const progressBars = { @@ -96,7 +100,7 @@ export function osProgressHandler(step: InitializeEmitter) { step.on('burn', state => progressBars[state.type].update(state)); - return Promise.promisify(rindle.wait)(step); + return waitStreamAsync(step); } export function getArchAndDeviceType(applicationName: string): Promise<{ arch: string, device_type: string }> { @@ -104,9 +108,9 @@ export function getArchAndDeviceType(applicationName: string): Promise<{ arch: s getApplication(applicationName), resin.models.config.getDeviceTypes(), function (app, deviceTypes) { - let config = _.find(deviceTypes, { slug: app.device_type }); + const config = _.find(deviceTypes, { slug: app.device_type }); - if (config == null) { + if (!config) { throw new Error('Could not read application information!'); } @@ -116,11 +120,11 @@ export function getArchAndDeviceType(applicationName: string): Promise<{ arch: s } 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)) { + // 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]); } diff --git a/lib/utils/logger.ts b/lib/utils/logger.ts index a8ba0f56..f14c699e 100644 --- a/lib/utils/logger.ts +++ b/lib/utils/logger.ts @@ -33,11 +33,9 @@ export class Logger { 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); } + _.forEach(this.streams, function(stream, key) { + if (key !== 'debug' || process.env.DEBUG) { + stream.pipe(process.stdout); } }); diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index 37ccef15..e2daa81c 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -25,8 +25,8 @@ import messages = require('./messages'); const resin = ResinSdk.fromSharedOptions(); -export function authenticate(options: {}) { - form.run([{ +export function authenticate(options: {}): Promise { + return form.run([{ message: 'Email:', name: 'email', type: 'input', @@ -44,13 +44,17 @@ export function authenticate(options: {}) { return form.ask({ message: 'Two factor auth challenge:', name: 'code', - type: 'input'}).then(resin.auth.twoFactor.challenge) - .catch((error: any) => resin.auth.logout().then(() => { - if ((error.name === 'ResinRequestError') && (error.statusCode === 401)) { - throw new Error('Invalid two factor authentication code'); - } - throw error; - })); + 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; + }); + }); }); } @@ -132,8 +136,10 @@ export function selectOrCreateApplication() { if (!hasAnyApplications) return; return resin.models.application.getAll().then((applications) => { - let appOptions: { name: string, value: string | null }[]; - appOptions = _.map(applications, application => ({ + const appOptions = _.map< + ResinSdk.Application, + { name: string, value: string | null } + >(applications, application => ({ name: `${application.app_name} (${application.device_type})`, value: application.app_name, })); @@ -150,7 +156,9 @@ export function selectOrCreateApplication() { }); }); }).then((application) => { - if (application != null) return application; + if (application) { + return application; + } return form.ask({ message: 'Choose a Name for your new application', @@ -189,13 +197,13 @@ export function awaitDevice(uuid: string) { export function inferOrSelectDevice(preferredUuid: string) { return resin.models.device.getAll() - .filter((device: ResinSdk.Device) => device.is_online) - .then((onlineDevices: ResinSdk.Device[]) => { + .filter((device) => device.is_online) + .then((onlineDevices) => { if (_.isEmpty(onlineDevices)) { throw new Error('You don\'t have any devices online'); } - let defaultUuid = Array.from(_.map(onlineDevices, 'uuid')).includes(preferredUuid) ? + const defaultUuid = _.map(onlineDevices, 'uuid').includes(preferredUuid) ? preferredUuid : onlineDevices[0].uuid; diff --git a/lib/utils/streams.ts b/lib/utils/streams.ts index 7dd9cdd8..aa580e8f 100644 --- a/lib/utils/streams.ts +++ b/lib/utils/streams.ts @@ -1,5 +1,3 @@ -import { ReadStream } from 'fs'; - export async function buffer(stream: NodeJS.ReadableStream, bufferFile: string) { const Promise = await import('bluebird'); const fs = await import('fs'); @@ -7,14 +5,15 @@ export async function buffer(stream: NodeJS.ReadableStream, bufferFile: string) const fileWriteStream = fs.createWriteStream(bufferFile); return new Promise(function(resolve, reject) { - return stream + 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); + const stream = fs.createReadStream(bufferFile); + + stream + .on('open', () => resolve(stream)) + .on('error', reject); })); } diff --git a/lib/utils/update.ts b/lib/utils/update.ts index e6ed56b4..a34624e6 100644 --- a/lib/utils/update.ts +++ b/lib/utils/update.ts @@ -34,18 +34,14 @@ if (!isRoot()) { }); } -export function hasAvailableUpdate() { - return notifier != null; -} - export function notify() { - if (!exports.hasAvailableUpdate()) { + if (!notifier) { return; } notifier.notify({ defer: false }); if (notifier.update != null) { - return console.log('Notice that you might need administrator privileges depending on your setup\n'); + console.log('Notice that you might need administrator privileges depending on your setup\n'); } } diff --git a/typings/resin-device-init.d.ts b/typings/resin-device-init.d.ts index 1fdae108..2ba03252 100644 --- a/typings/resin-device-init.d.ts +++ b/typings/resin-device-init.d.ts @@ -1,4 +1,5 @@ declare module 'resin-device-init' { + import * as Promise from 'bluebird'; import { EventEmitter } from 'events'; interface OperationState { diff --git a/typings/resin-sdk-preconfigured.d.ts b/typings/resin-sdk-preconfigured.d.ts index 82e14041..57afac5e 100644 --- a/typings/resin-sdk-preconfigured.d.ts +++ b/typings/resin-sdk-preconfigured.d.ts @@ -1,4 +1,4 @@ -declare module 'resin-sdk-preconfiguredasd' { +declare module 'resin-sdk-preconfigured' { import { ResinSDK } from 'resin-sdk'; let sdk: ResinSDK; export = sdk; From 4b74e8ec70ec55469619ee7351b5e4a8277db193 Mon Sep 17 00:00:00 2001 From: "resin-io-versionbot[bot]" Date: Tue, 9 Jan 2018 21:00:55 +0000 Subject: [PATCH 7/7] v6.12.3 --- CHANGELOG.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) 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/package.json b/package.json index 00ebe6c6..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", @@ -147,4 +147,4 @@ "optionalDependencies": { "removedrive": "^1.0.0" } -} +} \ No newline at end of file