mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-24 07:46:39 +00:00
Convert most of utils to TypeScript
Change-Type: patch
This commit is contained in:
parent
4b511c47f0
commit
ffffd447f2
@ -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?
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
@ -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);
|
||||
}
|
||||
};
|
@ -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');
|
@ -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)
|
39
lib/errors.ts
Normal file
39
lib/errors.ts
Normal file
@ -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<string, Error>(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));
|
||||
};
|
@ -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()
|
42
lib/events.ts
Normal file
42
lib/events.ts
Normal file
@ -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<any>(() => 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);
|
||||
};
|
@ -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
|
109
lib/utils/config.ts
Normal file
109
lib/utils/config.ts
Normal file
@ -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;
|
||||
});
|
||||
};
|
@ -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 ]
|
||||
}
|
146
lib/utils/helpers.ts
Normal file
146
lib/utils/helpers.ts
Normal file
@ -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<ResinSdk.DeviceType> {
|
||||
// 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 ]
|
||||
};
|
||||
}
|
||||
};
|
@ -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)
|
66
lib/utils/logger.ts
Normal file
66
lib/utils/logger.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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)
|
228
lib/utils/patterns.ts
Normal file
228
lib/utils/patterns.ts
Normal file
@ -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<void> => {
|
||||
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);
|
||||
};
|
@ -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)
|
||||
})
|
||||
}
|
@ -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)
|
20
lib/utils/streams.ts
Normal file
20
lib/utils/streams.ts
Normal file
@ -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);
|
||||
}));
|
||||
};
|
@ -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')
|
51
lib/utils/update.ts
Normal file
51
lib/utils/update.ts
Normal file
@ -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');
|
||||
}
|
||||
};
|
@ -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';
|
||||
}
|
||||
|
10
package.json
10
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",
|
||||
|
@ -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",
|
||||
|
36
typings/capitano.d.ts
vendored
Normal file
36
typings/capitano.d.ts
vendored
Normal file
@ -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<P = {}, O = {}> {
|
||||
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
|
||||
};
|
||||
}
|
1
typings/mixpanel.d.ts
vendored
Normal file
1
typings/mixpanel.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'mixpanel';
|
4
typings/nplugm.d.ts
vendored
Normal file
4
typings/nplugm.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module 'nplugm' {
|
||||
import Promise = require('bluebird');
|
||||
export function list(regexp: RegExp): Promise<Array<string>>;
|
||||
}
|
4
typings/package.json.d.ts
vendored
Normal file
4
typings/package.json.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module '*/package.json' {
|
||||
export const name: string;
|
||||
export const version: string;
|
||||
}
|
3
typings/president.d.ts
vendored
Normal file
3
typings/president.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
declare module 'president' {
|
||||
export function execute(command: string[], callback: (err: Error) => void): void;
|
||||
}
|
1
typings/resin-device-config.d.ts
vendored
Normal file
1
typings/resin-device-config.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'resin-device-config';
|
59
typings/resin-device-init.d.ts
vendored
Normal file
59
typings/resin-device-init.d.ts
vendored
Normal file
@ -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<InitializeEmitter>
|
||||
}
|
5
typings/resin-image-fs.d.ts
vendored
Normal file
5
typings/resin-image-fs.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module 'resin-image-fs' {
|
||||
import Promise = require('bluebird');
|
||||
|
||||
export function read(options: {}): Promise<NodeJS.ReadableStream>;
|
||||
}
|
5
typings/resin-sdk-preconfigured.d.ts
vendored
Normal file
5
typings/resin-sdk-preconfigured.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module 'resin-sdk-preconfiguredasd' {
|
||||
import { ResinSDK } from 'resin-sdk';
|
||||
let sdk: ResinSDK;
|
||||
export = sdk;
|
||||
}
|
13
typings/rindle.d.ts
vendored
Normal file
13
typings/rindle.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
53
typings/update-notifier.d.ts
vendored
Normal file
53
typings/update-notifier.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user