chore: follow standardjs guidelines (#1664)

- Extend the `standard` ESLint configuration
- Remove ESLint rules that are defined in the `standard` configuration
- Get rid of semi-colons

See: https://github.com/resin-io/etcher/pull/1657
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
This commit is contained in:
Juan Cruz Viotti 2017-08-03 09:01:54 -04:00 committed by GitHub
parent 5c19b70e83
commit d8e31665a0
134 changed files with 5337 additions and 5391 deletions

View File

@ -7,7 +7,9 @@ env:
plugins: plugins:
- lodash - lodash
- jsdoc - jsdoc
extends: 'eslint:recommended' extends: 'standard'
parserOptions:
sourceType: 'script'
settings: settings:
jsdoc: jsdoc:
additionalTagNames: additionalTagNames:
@ -17,63 +19,16 @@ rules:
# Possible Errors # Possible Errors
no-cond-assign:
- error
no-console: no-console:
- off - off
no-constant-condition:
- error
no-control-regex:
- error
no-debugger:
- error
no-dupe-args:
- error
no-dupe-keys:
- error
no-duplicate-case:
- error
no-empty: no-empty:
- error - error
no-empty-character-class:
- error
no-ex-assign:
- error
no-extra-boolean-cast:
- error
no-extra-parens:
- error
no-extra-semi: no-extra-semi:
- error - error
no-func-assign:
- error
no-inner-declarations:
- error
- both
no-invalid-regexp:
- error
no-irregular-whitespace:
- error
no-negated-in-lhs: no-negated-in-lhs:
- error - error
no-obj-calls:
- error
no-prototype-builtins: no-prototype-builtins:
- error - error
no-regex-spaces:
- error
no-sparse-arrays:
- error
no-template-curly-in-string:
- error
no-unexpected-multiline:
- error
no-unreachable:
- error
no-unsafe-finally:
- error
use-isnan:
- error
valid-jsdoc: valid-jsdoc:
- error - error
- requireReturn: false - requireReturn: false
@ -89,13 +44,9 @@ rules:
prefer: prefer:
arg: "param" arg: "param"
return: "returns" return: "returns"
valid-typeof:
- error
# Best Practices # Best Practices
accessor-pairs:
- error
array-callback-return: array-callback-return:
- error - error
block-scoped-var: block-scoped-var:
@ -110,19 +61,12 @@ rules:
- error - error
default-case: default-case:
- error - error
dot-location:
- error
- property
dot-notation: dot-notation:
- error - error
eqeqeq:
- error
guard-for-in: guard-for-in:
- error - error
no-alert: no-alert:
- error - error
no-caller:
- error
no-case-declarations: no-case-declarations:
- error - error
no-div-regex: no-div-regex:
@ -131,106 +75,43 @@ rules:
- error - error
no-empty-function: no-empty-function:
- error - error
no-empty-pattern:
- error
no-eq-null: no-eq-null:
- error - error
no-eval:
- error
no-extend-native:
- error
no-extra-bind:
- error
no-extra-label: no-extra-label:
- error - error
no-fallthrough:
- error
no-floating-decimal:
- error
no-global-assign:
- error
no-implicit-coercion: no-implicit-coercion:
- error - error
no-implicit-globals: no-implicit-globals:
- error - error
no-implied-eval:
- error
no-iterator:
- error
no-labels:
- error
no-lone-blocks:
- error
no-loop-func: no-loop-func:
- error - error
no-magic-numbers: no-magic-numbers:
- error - error
no-multi-spaces:
- error
no-multi-str:
- error
no-native-reassign: no-native-reassign:
- error - error
no-new:
- error
no-new-func:
- error
no-new-wrappers:
- error
no-octal:
- error
no-octal-escape:
- error
no-param-reassign: no-param-reassign:
- error - error
no-proto:
- error
no-redeclare:
- error
no-restricted-properties: no-restricted-properties:
- error - error
- property: __proto__ - property: __proto__
no-return-assign:
- error
no-return-await: no-return-await:
- error - error
no-script-url: no-script-url:
- error - error
no-self-assign:
- error
no-self-compare:
- error
no-sequences:
- error
no-throw-literal:
- error
no-unmodified-loop-condition:
- error
no-unused-expressions: no-unused-expressions:
- error - error
no-unused-labels: no-unused-labels:
- error - error
no-useless-call:
- error
no-useless-concat: no-useless-concat:
- error - error
no-useless-escape:
- error
no-void: no-void:
- error - error
no-warning-comments: no-warning-comments:
- off - off
no-with:
- error
radix: radix:
- error - error
vars-on-top: vars-on-top:
- off - off
wrap-iife:
- error
- outside
yoda:
- error
# Strict mode # Strict mode
@ -245,21 +126,11 @@ rules:
- always - always
no-catch-shadow: no-catch-shadow:
- error - error
no-delete-var:
- error
no-label-var:
- error
no-restricted-globals: no-restricted-globals:
- error - error
- event - event
no-shadow: no-shadow:
- error - error
no-shadow-restricted-names:
- error
no-undef:
- error
no-undef-init:
- error
no-undefined: no-undefined:
- error - error
no-unused-vars: no-unused-vars:
@ -273,14 +144,8 @@ rules:
- error - error
global-require: global-require:
- off - off
handle-callback-err:
- error
no-mixed-requires: no-mixed-requires:
- error - error
no-new-require:
- error
no-path-concat:
- error
no-process-env: no-process-env:
- off - off
no-process-exit: no-process-exit:
@ -293,38 +158,20 @@ rules:
array-bracket-spacing: array-bracket-spacing:
- error - error
- always - always
block-spacing:
- error
brace-style:
- error
- 1tbs
camelcase:
- error
capitalized-comments: capitalized-comments:
- error - error
- always - always
- ignoreConsecutiveComments: true - ignoreConsecutiveComments: true
comma-dangle:
- error
- never
comma-spacing: comma-spacing:
- error - error
- before: false - before: false
after: true after: true
comma-style:
- error
- last
computed-property-spacing: computed-property-spacing:
- error - error
- never - never
consistent-this: consistent-this:
- error - error
- self - self
eol-last:
- error
func-call-spacing:
- error
- never
func-name-matching: func-name-matching:
- error - error
- always - always
@ -341,19 +188,6 @@ rules:
- min: 2 - min: 2
exceptions: exceptions:
- "_" - "_"
indent:
- error
- 2
- SwitchCase: 1
key-spacing:
- error
- beforeColon: false
afterColon: true
mode: strict
keyword-spacing:
- error
- before: true
after: true
line-comment-position: line-comment-position:
- error - error
- position: above - position: above
@ -390,14 +224,8 @@ rules:
multiline-ternary: multiline-ternary:
- error - error
- never - never
new-cap:
- error
new-parens:
- error
newline-per-chained-call: newline-per-chained-call:
- off - off
no-array-constructor:
- error
no-bitwise: no-bitwise:
- error - error
no-continue: no-continue:
@ -408,21 +236,12 @@ rules:
- error - error
no-mixed-operators: no-mixed-operators:
- error - error
no-mixed-spaces-and-tabs:
- error
no-multi-assign: no-multi-assign:
- error - error
no-multiple-empty-lines:
- error
- max: 1
maxEOF: 1
maxBOF: 0
no-negated-condition: no-negated-condition:
- error - error
no-nested-ternary: no-nested-ternary:
- error - error
no-new-object:
- error
no-plusplus: no-plusplus:
- error - error
no-restricted-syntax: no-restricted-syntax:
@ -431,46 +250,24 @@ rules:
- ForInStatement - ForInStatement
no-spaced-func: no-spaced-func:
- error - error
no-tabs:
- error
no-trailing-spaces:
- error
no-underscore-dangle: no-underscore-dangle:
- error - error
- allowAfterThis: false - allowAfterThis: false
no-unneeded-ternary:
- error
no-whitespace-before-property:
- error
object-curly-newline: object-curly-newline:
- error - error
- minProperties: 1 - minProperties: 1
object-curly-spacing: object-curly-spacing:
- error - error
- always - always
object-property-newline:
- error
one-var-declaration-per-line: one-var-declaration-per-line:
- error - error
- always - always
one-var:
- error
- never
operator-assignment: operator-assignment:
- error - error
- always - always
operator-linebreak:
- error
- after
padded-blocks:
- error
- never
quote-props: quote-props:
- error - error
- as-needed - as-needed
quotes:
- error
- single
require-jsdoc: require-jsdoc:
- error - error
- require: - require:
@ -478,32 +275,11 @@ rules:
ClassDeclaration: true ClassDeclaration: true
MethodDefinition: true MethodDefinition: true
ArrowFunctionExpression: true ArrowFunctionExpression: true
semi:
- error
- always
semi-spacing:
- error
- before: false
after: true
space-before-blocks:
- error
space-before-function-paren: space-before-function-paren:
- error - error
- anonymous: always - anonymous: always
named: always named: always
asyncArrow: never asyncArrow: never
space-in-parens:
- error
- never
space-infix-ops:
- error
space-unary-ops:
- error
- words: true
nonwords: false
spaced-comment:
- error
- always
template-tag-spacing: template-tag-spacing:
- error - error
- always - always
@ -522,32 +298,12 @@ rules:
- error - error
- before: true - before: true
after: true after: true
constructor-super:
- error
generator-star-spacing: generator-star-spacing:
- error - error
- before: true - before: true
after: false after: false
no-class-assign:
- error
no-confusing-arrow: no-confusing-arrow:
- error - error
no-const-assign:
- error
no-dupe-class-members:
- error
no-duplicate-imports:
- error
no-new-symbol:
- error
no-this-before-super:
- error
no-useless-computed-key:
- error
no-useless-constructor:
- error
no-useless-rename:
- error
no-var: no-var:
- error - error
object-shorthand: object-shorthand:
@ -570,17 +326,8 @@ rules:
- allowNamedFunctions: false - allowNamedFunctions: false
require-yield: require-yield:
- error - error
rest-spread-spacing:
- error
template-curly-spacing:
- error
- never
symbol-description: symbol-description:
- error - error
yield-star-spacing:
- error
- before: true
after: false
# Lodash # Lodash

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @summary Get the explicit boolean form of an argument * @summary Get the explicit boolean form of an argument
@ -44,19 +44,19 @@ const _ = require('lodash');
exports.getBooleanArgumentForm = (argumentName, value) => { exports.getBooleanArgumentForm = (argumentName, value) => {
const prefix = _.attempt(() => { const prefix = _.attempt(() => {
if (!value) { if (!value) {
return '--no-'; return '--no-'
} }
const SHORT_OPTION_LENGTH = 1; const SHORT_OPTION_LENGTH = 1
if (_.size(argumentName) === SHORT_OPTION_LENGTH) { if (_.size(argumentName) === SHORT_OPTION_LENGTH) {
return '-'; return '-'
} }
return '--'; return '--'
}); })
return prefix + argumentName; return prefix + argumentName
}; }
/** /**
* @summary Get CLI writer arguments * @summary Get CLI writer arguments
@ -94,7 +94,7 @@ exports.getArguments = (options) => {
exports.getBooleanArgumentForm('unmount', options.unmountOnSuccess), exports.getBooleanArgumentForm('unmount', options.unmountOnSuccess),
exports.getBooleanArgumentForm('check', options.validateWriteOnSuccess) exports.getBooleanArgumentForm('check', options.validateWriteOnSuccess)
]; ]
return argv; return argv
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const path = require('path'); const path = require('path')
/** /**
* @summary Child writer constants * @summary Child writer constants
@ -37,4 +37,4 @@ module.exports = {
*/ */
WRITER_PROXY_SCRIPT: path.join(__dirname, 'writer-proxy.js') WRITER_PROXY_SCRIPT: path.join(__dirname, 'writer-proxy.js')
}; }

View File

@ -14,17 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter
const _ = require('lodash'); const _ = require('lodash')
const childProcess = require('child_process'); const childProcess = require('child_process')
const ipc = require('node-ipc'); const ipc = require('node-ipc')
const rendererUtils = require('./renderer-utils'); const rendererUtils = require('./renderer-utils')
const cli = require('./cli'); const cli = require('./cli')
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants')
const EXIT_CODES = require('../shared/exit-codes'); const EXIT_CODES = require('../shared/exit-codes')
const robot = require('../shared/robot'); const robot = require('../shared/robot')
/** /**
* @summary Perform a write * @summary Perform a write
@ -57,7 +57,7 @@ const robot = require('../shared/robot');
* }); * });
*/ */
exports.write = (image, drive, options) => { exports.write = (image, drive, options) => {
const emitter = new EventEmitter(); const emitter = new EventEmitter()
const argv = cli.getArguments({ const argv = cli.getArguments({
entryPoint: rendererUtils.getApplicationEntryPoint(), entryPoint: rendererUtils.getApplicationEntryPoint(),
@ -65,17 +65,17 @@ exports.write = (image, drive, options) => {
device: drive.device, device: drive.device,
validateWriteOnSuccess: options.validateWriteOnSuccess, validateWriteOnSuccess: options.validateWriteOnSuccess,
unmountOnSuccess: options.unmountOnSuccess unmountOnSuccess: options.unmountOnSuccess
}); })
// There might be multiple Etcher instances running at // There might be multiple Etcher instances running at
// the same time, therefore we must ensure each IPC // the same time, therefore we must ensure each IPC
// server/client has a different name. // server/client has a different name.
process.env.IPC_SERVER_ID = `etcher-server-${process.pid}`; process.env.IPC_SERVER_ID = `etcher-server-${process.pid}`
process.env.IPC_CLIENT_ID = `etcher-client-${process.pid}`; process.env.IPC_CLIENT_ID = `etcher-client-${process.pid}`
ipc.config.id = process.env.IPC_SERVER_ID; ipc.config.id = process.env.IPC_SERVER_ID
ipc.config.silent = true; ipc.config.silent = true
ipc.serve(); ipc.serve()
/** /**
* @summary Safely terminate the IPC server * @summary Safely terminate the IPC server
@ -91,11 +91,11 @@ exports.write = (image, drive, options) => {
// just stops receiving any further connections, // just stops receiving any further connections,
// but remains open if there are active ones. // but remains open if there are active ones.
_.each(ipc.server.sockets, (socket) => { _.each(ipc.server.sockets, (socket) => {
socket.destroy(); socket.destroy()
}); })
ipc.server.stop(); ipc.server.stop()
}; }
/** /**
* @summary Emit an error to the client * @summary Emit an error to the client
@ -108,9 +108,9 @@ exports.write = (image, drive, options) => {
* emitError(new Error('foo bar')); * emitError(new Error('foo bar'));
*/ */
const emitError = (error) => { const emitError = (error) => {
terminateServer(); terminateServer()
emitter.emit('error', error); emitter.emit('error', error)
}; }
/** /**
* @summary Bridge robot message to the child writer caller * @summary Bridge robot message to the child writer caller
@ -127,7 +127,7 @@ exports.write = (image, drive, options) => {
const bridgeRobotMessage = (message) => { const bridgeRobotMessage = (message) => {
const parsedMessage = _.attempt(() => { const parsedMessage = _.attempt(() => {
if (robot.isMessage(message)) { if (robot.isMessage(message)) {
return robot.parseMessage(message); return robot.parseMessage(message)
} }
// Don't be so strict. If a message doesn't look like // Don't be so strict. If a message doesn't look like
@ -135,83 +135,83 @@ exports.write = (image, drive, options) => {
// for debugging purposes. // for debugging purposes.
return robot.parseMessage(robot.buildMessage(robot.COMMAND.LOG, { return robot.parseMessage(robot.buildMessage(robot.COMMAND.LOG, {
message message
})); }))
}); })
if (_.isError(parsedMessage)) { if (_.isError(parsedMessage)) {
emitError(parsedMessage); emitError(parsedMessage)
return; return
} }
try { try {
// These are lighweight accessor methods for // These are lighweight accessor methods for
// the properties of the parsed message // the properties of the parsed message
const messageCommand = robot.getCommand(parsedMessage); const messageCommand = robot.getCommand(parsedMessage)
const messageData = robot.getData(parsedMessage); const messageData = robot.getData(parsedMessage)
// The error object is decomposed by the CLI for serialisation // The error object is decomposed by the CLI for serialisation
// purposes. We compose it back to an `Error` here in order // purposes. We compose it back to an `Error` here in order
// to provide better encapsulation. // to provide better encapsulation.
if (messageCommand === robot.COMMAND.ERROR) { if (messageCommand === robot.COMMAND.ERROR) {
emitError(robot.recomposeErrorMessage(parsedMessage)); emitError(robot.recomposeErrorMessage(parsedMessage))
} else if (messageCommand === robot.COMMAND.LOG) { } else if (messageCommand === robot.COMMAND.LOG) {
// If the message data is an object and it contains a // If the message data is an object and it contains a
// message string then log the message string only. // message string then log the message string only.
if (_.isPlainObject(messageData) && _.isString(messageData.message)) { if (_.isPlainObject(messageData) && _.isString(messageData.message)) {
console.log(messageData.message); console.log(messageData.message)
} else { } else {
console.log(messageData); console.log(messageData)
} }
} else { } else {
emitter.emit(messageCommand, messageData); emitter.emit(messageCommand, messageData)
} }
} catch (error) { } catch (error) {
emitError(error); emitError(error)
}
} }
};
ipc.server.on('error', emitError); ipc.server.on('error', emitError)
ipc.server.on('message', bridgeRobotMessage); ipc.server.on('message', bridgeRobotMessage)
ipc.server.on('start', () => { ipc.server.on('start', () => {
const child = childProcess.fork(CONSTANTS.WRITER_PROXY_SCRIPT, argv, { const child = childProcess.fork(CONSTANTS.WRITER_PROXY_SCRIPT, argv, {
silent: true, silent: true,
env: process.env env: process.env
}); })
child.stdout.on('data', (data) => { child.stdout.on('data', (data) => {
console.info(`WRITER: ${data.toString()}`); console.info(`WRITER: ${data.toString()}`)
}); })
child.stderr.on('data', (data) => { child.stderr.on('data', (data) => {
bridgeRobotMessage(data.toString()); bridgeRobotMessage(data.toString())
// This function causes the `close` event to be emitted // This function causes the `close` event to be emitted
child.kill(); child.kill()
}); })
child.on('error', emitError); child.on('error', emitError)
child.on('close', (code) => { child.on('close', (code) => {
terminateServer(); terminateServer()
if (code === EXIT_CODES.CANCELLED) { if (code === EXIT_CODES.CANCELLED) {
return emitter.emit('done', { return emitter.emit('done', {
cancelled: true cancelled: true
}); })
} }
// We shouldn't emit the `done` event manually here // We shouldn't emit the `done` event manually here
// since the writer process will take care of it. // since the writer process will take care of it.
if (code === EXIT_CODES.SUCCESS || code === EXIT_CODES.VALIDATION_ERROR) { if (code === EXIT_CODES.SUCCESS || code === EXIT_CODES.VALIDATION_ERROR) {
return null; return null
} }
return emitError(new Error(`Child process exited with error code: ${code}`)); return emitError(new Error(`Child process exited with error code: ${code}`))
}); })
}); })
ipc.server.start(); ipc.server.start()
return emitter; return emitter
}; }

View File

@ -14,16 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* This file is only meant to be loaded by the renderer process. * This file is only meant to be loaded by the renderer process.
*/ */
const path = require('path'); const path = require('path')
const isRunningInAsar = require('electron-is-running-in-asar'); const isRunningInAsar = require('electron-is-running-in-asar')
const electron = require('electron'); const electron = require('electron')
const CONSTANTS = require('./constants'); const CONSTANTS = require('./constants')
/** /**
* @summary Get application entry point * @summary Get application entry point
@ -37,14 +37,14 @@ const CONSTANTS = require('./constants');
*/ */
exports.getApplicationEntryPoint = () => { exports.getApplicationEntryPoint = () => {
if (isRunningInAsar()) { if (isRunningInAsar()) {
return path.join(process.resourcesPath, 'app.asar'); return path.join(process.resourcesPath, 'app.asar')
} }
const ENTRY_POINT_ARGV_INDEX = 1; const ENTRY_POINT_ARGV_INDEX = 1
const relativeEntryPoint = electron.remote.process.argv[ENTRY_POINT_ARGV_INDEX]; const relativeEntryPoint = electron.remote.process.argv[ENTRY_POINT_ARGV_INDEX]
// On GNU/Linux, `pkexec` resolves relative paths // On GNU/Linux, `pkexec` resolves relative paths
// from `/root`, therefore we pass an absolute path, // from `/root`, therefore we pass an absolute path,
// in order to be on the safe side. // in order to be on the safe side.
return path.join(CONSTANTS.PROJECT_ROOT, relativeEntryPoint); return path.join(CONSTANTS.PROJECT_ROOT, relativeEntryPoint)
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @summary Split stringified object lines * @summary Split stringified object lines
@ -41,5 +41,5 @@ exports.splitObjectLines = (lines) => {
.split(/((?:[^\n"']|"[^"]*"|'[^']*')+)/) .split(/((?:[^\n"']|"[^"]*"|'[^']*')+)/)
.map(_.trim) .map(_.trim)
.reject(_.isEmpty) .reject(_.isEmpty)
.value(); .value()
}; }

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const childProcess = require('child_process'); const childProcess = require('child_process')
const ipc = require('node-ipc'); const ipc = require('node-ipc')
const _ = require('lodash'); const _ = require('lodash')
const os = require('os'); const os = require('os')
const path = require('path'); const path = require('path')
const utils = require('./utils'); const utils = require('./utils')
const EXIT_CODES = require('../shared/exit-codes'); const EXIT_CODES = require('../shared/exit-codes')
const robot = require('../shared/robot'); const robot = require('../shared/robot')
const permissions = require('../shared/permissions'); const permissions = require('../shared/permissions')
const packageJSON = require('../../package.json'); const packageJSON = require('../../package.json')
// This script is in charge of spawning the writer process and // This script is in charge of spawning the writer process and
// ensuring it has the necessary privileges. It might look a bit // ensuring it has the necessary privileges. It might look a bit
@ -43,7 +43,7 @@ const packageJSON = require('../../package.json');
* @private * @private
* @type {String} * @type {String}
*/ */
const executable = _.first(process.argv); const executable = _.first(process.argv)
/** /**
* @summary The first index that represents an actual option argument * @summary The first index that represents an actual option argument
@ -54,7 +54,7 @@ const executable = _.first(process.argv);
* @description * @description
* The first arguments are usually the program executable itself, etc. * The first arguments are usually the program executable itself, etc.
*/ */
const OPTIONS_INDEX_START = 2; const OPTIONS_INDEX_START = 2
/** /**
* @summary The list of Etcher argument options * @summary The list of Etcher argument options
@ -62,11 +62,11 @@ const OPTIONS_INDEX_START = 2;
* @private * @private
* @type {String[]} * @type {String[]}
*/ */
const etcherArguments = process.argv.slice(OPTIONS_INDEX_START); const etcherArguments = process.argv.slice(OPTIONS_INDEX_START)
permissions.isElevated().then((elevated) => { permissions.isElevated().then((elevated) => {
if (!elevated) { if (!elevated) {
console.log('Attempting to elevate'); console.log('Attempting to elevate')
const commandArguments = _.attempt(() => { const commandArguments = _.attempt(() => {
if (os.platform() === 'linux' && process.env.APPIMAGE && process.env.APPDIR) { if (os.platform() === 'linux' && process.env.APPIMAGE && process.env.APPDIR) {
@ -75,16 +75,16 @@ permissions.isElevated().then((elevated) => {
const translatedArguments = _.chain(process.argv) const translatedArguments = _.chain(process.argv)
.tail() .tail()
.invokeMap('replace', path.join(process.env.APPDIR, 'usr/'), '') .invokeMap('replace', path.join(process.env.APPDIR, 'usr/'), '')
.value(); .value()
return _.concat([ process.env.APPIMAGE ], translatedArguments); return _.concat([ process.env.APPIMAGE ], translatedArguments)
} }
return process.argv; return process.argv
}); })
// For debugging purposes // For debugging purposes
console.log(`Running: ${commandArguments.join(' ')}`); console.log(`Running: ${commandArguments.join(' ')}`)
return permissions.elevateCommand(commandArguments, { return permissions.elevateCommand(commandArguments, {
applicationName: packageJSON.displayName, applicationName: packageJSON.displayName,
@ -101,16 +101,16 @@ permissions.isElevated().then((elevated) => {
} }
}).then((results) => { }).then((results) => {
if (results.cancelled) { if (results.cancelled) {
process.exit(EXIT_CODES.CANCELLED); process.exit(EXIT_CODES.CANCELLED)
} }
}); })
} }
console.log('Re-spawning with elevation'); console.log('Re-spawning with elevation')
return new Bluebird((resolve, reject) => { return new Bluebird((resolve, reject) => {
ipc.config.id = process.env.IPC_CLIENT_ID; ipc.config.id = process.env.IPC_CLIENT_ID
ipc.config.silent = true; ipc.config.silent = true
// > If set to 0, the client will NOT try to reconnect. // > If set to 0, the client will NOT try to reconnect.
// See https://github.com/RIAEvangelist/node-ipc/ // See https://github.com/RIAEvangelist/node-ipc/
@ -118,10 +118,10 @@ permissions.isElevated().then((elevated) => {
// The purpose behind this change is for this process // The purpose behind this change is for this process
// to emit a "disconnect" event as soon as the GUI // to emit a "disconnect" event as soon as the GUI
// process is closed, so we can kill the CLI as well. // process is closed, so we can kill the CLI as well.
ipc.config.stopRetrying = 0; ipc.config.stopRetrying = 0
ipc.connectTo(process.env.IPC_SERVER_ID, () => { ipc.connectTo(process.env.IPC_SERVER_ID, () => {
ipc.of[process.env.IPC_SERVER_ID].on('error', reject); ipc.of[process.env.IPC_SERVER_ID].on('error', reject)
ipc.of[process.env.IPC_SERVER_ID].on('connect', () => { ipc.of[process.env.IPC_SERVER_ID].on('connect', () => {
const child = childProcess.spawn(executable, etcherArguments, { const child = childProcess.spawn(executable, etcherArguments, {
env: { env: {
@ -138,11 +138,11 @@ permissions.isElevated().then((elevated) => {
MOUNTUTILS_DEBUG: 1 MOUNTUTILS_DEBUG: 1
} }
}); })
ipc.of[process.env.IPC_SERVER_ID].on('disconnect', _.bind(child.kill, child)); ipc.of[process.env.IPC_SERVER_ID].on('disconnect', _.bind(child.kill, child))
child.on('error', reject); child.on('error', reject)
child.on('close', resolve); child.on('close', resolve)
/** /**
* @summary Emit an object message to the IPC server * @summary Emit an object message to the IPC server
@ -162,18 +162,18 @@ permissions.isElevated().then((elevated) => {
// Trying to parse multiple JSON objects separated by new lines will // Trying to parse multiple JSON objects separated by new lines will
// of course make the parser confused, causing errors later on. // of course make the parser confused, causing errors later on.
_.each(utils.splitObjectLines(data.toString()), (object) => { _.each(utils.splitObjectLines(data.toString()), (object) => {
ipc.of[process.env.IPC_SERVER_ID].emit('message', object); ipc.of[process.env.IPC_SERVER_ID].emit('message', object)
}); })
}; }
child.stdout.on('data', emitMessage); child.stdout.on('data', emitMessage)
child.stderr.on('data', emitMessage); child.stderr.on('data', emitMessage)
}); })
}); })
}).then((exitCode) => { }).then((exitCode) => {
process.exit(exitCode); process.exit(exitCode)
}); })
}).catch((error) => { }).catch((error) => {
robot.printError(error); robot.printError(error)
process.exit(EXIT_CODES.GENERAL_ERROR); process.exit(EXIT_CODES.GENERAL_ERROR)
}); })

View File

@ -14,32 +14,32 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const path = require('path'); const path = require('path')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const visuals = require('resin-cli-visuals'); const visuals = require('resin-cli-visuals')
const form = require('resin-cli-form'); const form = require('resin-cli-form')
const drivelist = Bluebird.promisifyAll(require('drivelist')); const drivelist = Bluebird.promisifyAll(require('drivelist'))
const writer = require('./writer'); const writer = require('./writer')
const utils = require('./utils'); const utils = require('./utils')
const options = require('./options'); const options = require('./options')
const robot = require('../shared/robot'); const robot = require('../shared/robot')
const messages = require('../shared/messages'); const messages = require('../shared/messages')
const EXIT_CODES = require('../shared/exit-codes'); const EXIT_CODES = require('../shared/exit-codes')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
const permissions = require('../shared/permissions'); const permissions = require('../shared/permissions')
const ARGV_IMAGE_PATH_INDEX = 0; const ARGV_IMAGE_PATH_INDEX = 0
const imagePath = options._[ARGV_IMAGE_PATH_INDEX]; const imagePath = options._[ARGV_IMAGE_PATH_INDEX]
permissions.isElevated().then((elevated) => { permissions.isElevated().then((elevated) => {
if (!elevated) { if (!elevated) {
throw errors.createUserError({ throw errors.createUserError({
title: messages.error.elevationRequired(), title: messages.error.elevationRequired(),
description: 'This tool requires special permissions to write to external drives' description: 'This tool requires special permissions to write to external drives'
}); })
} }
return form.run([ return form.run([
@ -64,30 +64,30 @@ permissions.isElevated().then((elevated) => {
yes: robot.isEnabled(process.env) || options.yes || null yes: robot.isEnabled(process.env) || options.yes || null
} }
}); })
}).then((answers) => { }).then((answers) => {
if (!answers.yes) { if (!answers.yes) {
throw errors.createUserError({ throw errors.createUserError({
title: 'Aborted', title: 'Aborted',
description: 'We can\'t proceed without confirmation' description: 'We can\'t proceed without confirmation'
}); })
} }
const progressBars = { const progressBars = {
write: new visuals.Progress('Flashing'), write: new visuals.Progress('Flashing'),
check: new visuals.Progress('Validating') check: new visuals.Progress('Validating')
}; }
return drivelist.listAsync().then((drives) => { return drivelist.listAsync().then((drives) => {
const selectedDrive = _.find(drives, { const selectedDrive = _.find(drives, {
device: answers.drive device: answers.drive
}); })
if (!selectedDrive) { if (!selectedDrive) {
throw errors.createUserError({ throw errors.createUserError({
title: 'The selected drive was not found', title: 'The selected drive was not found',
description: `We can't find ${answers.drive} in your system. Did you unplug the drive?` description: `We can't find ${answers.drive} in your system. Did you unplug the drive?`
}); })
} }
return writer.writeImage(imagePath, selectedDrive, { return writer.writeImage(imagePath, selectedDrive, {
@ -100,52 +100,52 @@ permissions.isElevated().then((elevated) => {
percentage: Math.floor(state.percentage), percentage: Math.floor(state.percentage),
eta: state.eta, eta: state.eta,
speed: Math.floor(state.speed) speed: Math.floor(state.speed)
}); })
} else { } else {
progressBars[state.type].update(state); progressBars[state.type].update(state)
} }
}).then((results) => { }).then((results) => {
return { return {
imagePath, imagePath,
flash: results, flash: results,
drive: selectedDrive drive: selectedDrive
}; }
}); })
}); })
}).then((results) => { }).then((results) => {
return Bluebird.try(() => { return Bluebird.try(() => {
if (robot.isEnabled(process.env)) { if (robot.isEnabled(process.env)) {
return robot.printMessage('done', { return robot.printMessage('done', {
sourceChecksum: results.flash.sourceChecksum sourceChecksum: results.flash.sourceChecksum
}); })
} }
console.log(messages.info.flashComplete({ console.log(messages.info.flashComplete({
drive: results.drive, drive: results.drive,
imageBasename: path.basename(results.imagePath) imageBasename: path.basename(results.imagePath)
})); }))
if (results.flash.sourceChecksum) { if (results.flash.sourceChecksum) {
console.log(`Checksum: ${results.flash.sourceChecksum}`); console.log(`Checksum: ${results.flash.sourceChecksum}`)
} }
return Bluebird.resolve(); return Bluebird.resolve()
}).then(() => { }).then(() => {
process.exit(EXIT_CODES.SUCCESS); process.exit(EXIT_CODES.SUCCESS)
}); })
}).catch((error) => { }).catch((error) => {
return Bluebird.try(() => { return Bluebird.try(() => {
if (robot.isEnabled(process.env)) { if (robot.isEnabled(process.env)) {
return robot.printError(error); return robot.printError(error)
} }
utils.printError(error); utils.printError(error)
return Bluebird.resolve(); return Bluebird.resolve()
}).then(() => { }).then(() => {
if (error.code === 'EVALIDATION') { if (error.code === 'EVALIDATION') {
process.exit(EXIT_CODES.VALIDATION_ERROR); process.exit(EXIT_CODES.VALIDATION_ERROR)
} }
process.exit(EXIT_CODES.GENERAL_ERROR); process.exit(EXIT_CODES.GENERAL_ERROR)
}); })
}); })

View File

@ -14,16 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const fs = require('fs'); const fs = require('fs')
const yargs = require('yargs'); const yargs = require('yargs')
const utils = require('./utils'); const utils = require('./utils')
const robot = require('../shared/robot'); const robot = require('../shared/robot')
const EXIT_CODES = require('../shared/exit-codes'); const EXIT_CODES = require('../shared/exit-codes')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
const packageJSON = require('../../package.json'); const packageJSON = require('../../package.json')
/** /**
* @summary The minimum required number of CLI arguments * @summary The minimum required number of CLI arguments
@ -31,7 +31,7 @@ const packageJSON = require('../../package.json');
* @private * @private
* @type {Number} * @type {Number}
*/ */
const MINIMUM_NUMBER_OF_ARGUMENTS = 1; const MINIMUM_NUMBER_OF_ARGUMENTS = 1
/** /**
* @summary The index of the image argument * @summary The index of the image argument
@ -39,7 +39,7 @@ const MINIMUM_NUMBER_OF_ARGUMENTS = 1;
* @private * @private
* @type {Number} * @type {Number}
*/ */
const IMAGE_PATH_ARGV_INDEX = 0; const IMAGE_PATH_ARGV_INDEX = 0
/** /**
* @summary The first index that represents an actual option argument * @summary The first index that represents an actual option argument
@ -50,7 +50,7 @@ const IMAGE_PATH_ARGV_INDEX = 0;
* @description * @description
* The first arguments are usually the program executable itself, etc. * The first arguments are usually the program executable itself, etc.
*/ */
const OPTIONS_INDEX_START = 2; const OPTIONS_INDEX_START = 2
/** /**
* @summary Parsed CLI options and arguments * @summary Parsed CLI options and arguments
@ -69,8 +69,8 @@ module.exports = yargs
.epilogue([ .epilogue([
'Exit codes:', 'Exit codes:',
_.map(EXIT_CODES, (value, key) => { _.map(EXIT_CODES, (value, key) => {
const reason = _.map(_.split(key, '_'), _.capitalize).join(' '); const reason = _.map(_.split(key, '_'), _.capitalize).join(' ')
return ` ${value} - ${reason}`; return ` ${value} - ${reason}`
}).join('\n'), }).join('\n'),
'', '',
'If you need help, don\'t hesitate in contacting us at:', 'If you need help, don\'t hesitate in contacting us at:',
@ -95,32 +95,32 @@ module.exports = yargs
.fail((message, error) => { .fail((message, error) => {
const errorObject = error || errors.createUserError({ const errorObject = error || errors.createUserError({
title: message title: message
}); })
if (robot.isEnabled(process.env)) { if (robot.isEnabled(process.env)) {
robot.printError(errorObject); robot.printError(errorObject)
} else { } else {
yargs.showHelp(); yargs.showHelp()
utils.printError(errorObject); utils.printError(errorObject)
} }
process.exit(EXIT_CODES.GENERAL_ERROR); process.exit(EXIT_CODES.GENERAL_ERROR)
}) })
// Assert that image exists // Assert that image exists
.check((argv) => { .check((argv) => {
const imagePath = argv._[IMAGE_PATH_ARGV_INDEX]; const imagePath = argv._[IMAGE_PATH_ARGV_INDEX]
try { try {
fs.accessSync(imagePath); fs.accessSync(imagePath)
} catch (error) { } catch (error) {
throw errors.createUserError({ throw errors.createUserError({
title: 'Unable to access file', title: 'Unable to access file',
description: `The image ${imagePath} is not accessible` description: `The image ${imagePath} is not accessible`
}); })
} }
return true; return true
}) })
.check((argv) => { .check((argv) => {
@ -128,10 +128,10 @@ module.exports = yargs
throw errors.createUserError({ throw errors.createUserError({
title: 'Missing drive', title: 'Missing drive',
description: 'You need to explicitly pass a drive when enabling robot mode' description: 'You need to explicitly pass a drive when enabling robot mode'
}); })
} }
return true; return true
}) })
.options({ .options({
@ -168,4 +168,4 @@ module.exports = yargs
default: true default: true
} }
}) })
.parse(process.argv.slice(OPTIONS_INDEX_START)); .parse(process.argv.slice(OPTIONS_INDEX_START))

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const chalk = require('chalk'); const chalk = require('chalk')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
/** /**
* @summary Print an error to stderr * @summary Print an error to stderr
@ -30,18 +30,18 @@ const errors = require('../shared/errors');
* utils.printError(new Error('Oops!')); * utils.printError(new Error('Oops!'));
*/ */
exports.printError = (error) => { exports.printError = (error) => {
const title = errors.getTitle(error); const title = errors.getTitle(error)
const description = errors.getDescription(error, { const description = errors.getDescription(error, {
userFriendlyDescriptionsOnly: true userFriendlyDescriptionsOnly: true
}); })
console.error(chalk.red(title)); console.error(chalk.red(title))
if (description) { if (description) {
console.error(`\n${chalk.red(description)}`); console.error(`\n${chalk.red(description)}`)
} }
if (process.env.ETCHER_CLI_DEBUG && error.stack) { if (process.env.ETCHER_CLI_DEBUG && error.stack) {
console.error(`\n${chalk.red(error.stack)}`); console.error(`\n${chalk.red(error.stack)}`)
} }
}; }

View File

@ -14,23 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const imageWrite = require('etcher-image-write'); const imageWrite = require('etcher-image-write')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const fs = Bluebird.promisifyAll(require('fs')); const fs = Bluebird.promisifyAll(require('fs'))
const mountutils = Bluebird.promisifyAll(require('mountutils')); const mountutils = Bluebird.promisifyAll(require('mountutils'))
const os = require('os'); const os = require('os')
const imageStream = require('../image-stream'); const imageStream = require('../image-stream')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
const constraints = require('../shared/drive-constraints'); const constraints = require('../shared/drive-constraints')
/** /**
* @summary Timeout, in milliseconds, to wait before unmounting on success * @summary Timeout, in milliseconds, to wait before unmounting on success
* @constant * @constant
* @type {Number} * @type {Number}
*/ */
const UNMOUNT_ON_SUCCESS_TIMEOUT_MS = 2000; const UNMOUNT_ON_SUCCESS_TIMEOUT_MS = 2000
/** /**
* @summary Write an image to a disk drive * @summary Write an image to a disk drive
@ -67,19 +67,19 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
return Bluebird.try(() => { return Bluebird.try(() => {
// Unmounting a drive in Windows means we can't write to it anymore // Unmounting a drive in Windows means we can't write to it anymore
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
return Bluebird.resolve(); return Bluebird.resolve()
} }
return mountutils.unmountDiskAsync(drive.device); return mountutils.unmountDiskAsync(drive.device)
}).then(() => { }).then(() => {
return fs.openAsync(drive.raw, 'rs+'); return fs.openAsync(drive.raw, 'rs+')
}).then((driveFileDescriptor) => { }).then((driveFileDescriptor) => {
return imageStream.getFromFilePath(imagePath).then((image) => { return imageStream.getFromFilePath(imagePath).then((image) => {
if (!constraints.isDriveLargeEnough(drive, image)) { if (!constraints.isDriveLargeEnough(drive, image)) {
throw errors.createUserError({ throw errors.createUserError({
title: 'The image you selected is too big for this drive', title: 'The image you selected is too big for this drive',
description: 'Please connect a bigger drive and try again' description: 'Please connect a bigger drive and try again'
}); })
} }
return imageWrite.write({ return imageWrite.write({
@ -94,13 +94,13 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
transform: image.transform, transform: image.transform,
bmap: image.bmap, bmap: image.bmap,
bytesToZeroOutFromTheBeginning: image.bytesToZeroOutFromTheBeginning bytesToZeroOutFromTheBeginning: image.bytesToZeroOutFromTheBeginning
}); })
}).then((writer) => { }).then((writer) => {
return new Bluebird((resolve, reject) => { return new Bluebird((resolve, reject) => {
writer.on('progress', onProgress); writer.on('progress', onProgress)
writer.on('error', reject); writer.on('error', reject)
writer.on('done', resolve); writer.on('done', resolve)
}); })
}).tap(() => { }).tap(() => {
// Make sure the device stream file descriptor is closed // Make sure the device stream file descriptor is closed
// before returning control the the caller. Not closing // before returning control the the caller. Not closing
@ -109,7 +109,7 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
// right afterwards in some Windows 7 systems. // right afterwards in some Windows 7 systems.
return fs.closeAsync(driveFileDescriptor).then(() => { return fs.closeAsync(driveFileDescriptor).then(() => {
if (!options.unmountOnSuccess) { if (!options.unmountOnSuccess) {
return Bluebird.resolve(); return Bluebird.resolve()
} }
// Closing a file descriptor on a drive containing mountable // Closing a file descriptor on a drive containing mountable
@ -118,8 +118,8 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
// right afterwards. // right afterwards.
return Bluebird.delay(UNMOUNT_ON_SUCCESS_TIMEOUT_MS) return Bluebird.delay(UNMOUNT_ON_SUCCESS_TIMEOUT_MS)
.return(drive.device) .return(drive.device)
.then(mountutils.unmountDiskAsync); .then(mountutils.unmountDiskAsync)
}); })
}); })
}); })
}; }

View File

@ -18,33 +18,33 @@
* @module Etcher * @module Etcher
*/ */
'use strict'; 'use strict'
/* eslint-disable no-var */ /* eslint-disable no-var */
var angular = require('angular'); var angular = require('angular')
/* eslint-enable no-var */ /* eslint-enable no-var */
const electron = require('electron'); const electron = require('electron')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const semver = require('semver'); const semver = require('semver')
const EXIT_CODES = require('../shared/exit-codes'); const EXIT_CODES = require('../shared/exit-codes')
const messages = require('../shared/messages'); const messages = require('../shared/messages')
const s3Packages = require('../shared/s3-packages'); const s3Packages = require('../shared/s3-packages')
const release = require('../shared/release'); const release = require('../shared/release')
const store = require('../shared/store'); const store = require('../shared/store')
const packageJSON = require('../../package.json'); const packageJSON = require('../../package.json')
const flashState = require('../shared/models/flash-state'); const flashState = require('../shared/models/flash-state')
const settings = require('./models/settings'); const settings = require('./models/settings')
const windowProgress = require('./os/window-progress'); const windowProgress = require('./os/window-progress')
const analytics = require('./modules/analytics'); const analytics = require('./modules/analytics')
const updateNotifier = require('./components/update-notifier'); const updateNotifier = require('./components/update-notifier')
const availableDrives = require('../shared/models/available-drives'); const availableDrives = require('../shared/models/available-drives')
const selectionState = require('../shared/models/selection-state'); const selectionState = require('../shared/models/selection-state')
const driveScanner = require('./modules/drive-scanner'); const driveScanner = require('./modules/drive-scanner')
const osDialog = require('./os/dialog'); const osDialog = require('./os/dialog')
const exceptionReporter = require('./modules/exception-reporter'); const exceptionReporter = require('./modules/exception-reporter')
const app = angular.module('Etcher', [ const app = angular.module('Etcher', [
require('angular-ui-router'), require('angular-ui-router'),
@ -67,7 +67,7 @@ const app = angular.module('Etcher', [
// Utils // Utils
require('./utils/manifest-bind/manifest-bind') require('./utils/manifest-bind/manifest-bind')
]); ])
app.run(() => { app.run(() => {
console.log([ console.log([
@ -82,46 +82,46 @@ app.run(() => {
'Drop us a line at join+etcher@resin.io', 'Drop us a line at join+etcher@resin.io',
'', '',
`Version = ${packageJSON.version}, Type = ${packageJSON.packageType}` `Version = ${packageJSON.version}, Type = ${packageJSON.packageType}`
].join('\n')); ].join('\n'))
}); })
app.run(() => { app.run(() => {
const currentVersion = packageJSON.version; const currentVersion = packageJSON.version
analytics.logEvent('Application start', { analytics.logEvent('Application start', {
packageType: packageJSON.packageType, packageType: packageJSON.packageType,
version: currentVersion version: currentVersion
}); })
settings.load().then(() => { settings.load().then(() => {
const shouldCheckForUpdates = updateNotifier.shouldCheckForUpdates({ const shouldCheckForUpdates = updateNotifier.shouldCheckForUpdates({
currentVersion, currentVersion,
lastSleptUpdateNotifier: settings.get('lastSleptUpdateNotifier'), lastSleptUpdateNotifier: settings.get('lastSleptUpdateNotifier'),
lastSleptUpdateNotifierVersion: settings.get('lastSleptUpdateNotifierVersion') lastSleptUpdateNotifierVersion: settings.get('lastSleptUpdateNotifierVersion')
}); })
const currentReleaseType = release.getReleaseType(currentVersion); const currentReleaseType = release.getReleaseType(currentVersion)
const updatesEnabled = settings.get('updatesEnabled'); const updatesEnabled = settings.get('updatesEnabled')
if (!shouldCheckForUpdates || !updatesEnabled) { if (!shouldCheckForUpdates || !updatesEnabled) {
analytics.logEvent('Not checking for updates', { analytics.logEvent('Not checking for updates', {
shouldCheckForUpdates, shouldCheckForUpdates,
updatesEnabled, updatesEnabled,
releaseType: currentReleaseType releaseType: currentReleaseType
}); })
return Bluebird.resolve(); return Bluebird.resolve()
} }
const updateSemverRange = packageJSON.updates.semverRange; const updateSemverRange = packageJSON.updates.semverRange
const includeUnstableChannel = settings.get('includeUnstableUpdateChannel'); const includeUnstableChannel = settings.get('includeUnstableUpdateChannel')
analytics.logEvent('Checking for updates', { analytics.logEvent('Checking for updates', {
currentVersion, currentVersion,
releaseType: currentReleaseType, releaseType: currentReleaseType,
updateSemverRange, updateSemverRange,
includeUnstableChannel includeUnstableChannel
}); })
return s3Packages.getLatestVersion(currentReleaseType, { return s3Packages.getLatestVersion(currentReleaseType, {
range: updateSemverRange, range: updateSemverRange,
@ -130,8 +130,8 @@ app.run(() => {
if (semver.gte(currentVersion, latestVersion || '0.0.0')) { if (semver.gte(currentVersion, latestVersion || '0.0.0')) {
analytics.logEvent('Update notification skipped', { analytics.logEvent('Update notification skipped', {
reason: 'Latest version' reason: 'Latest version'
}); })
return Bluebird.resolve(); return Bluebird.resolve()
} }
// In case the internet connection is not good and checking the // In case the internet connection is not good and checking the
@ -142,24 +142,24 @@ app.run(() => {
if (selectionState.hasImage()) { if (selectionState.hasImage()) {
analytics.logEvent('Update notification skipped', { analytics.logEvent('Update notification skipped', {
reason: 'Image selected' reason: 'Image selected'
}); })
return Bluebird.resolve(); return Bluebird.resolve()
} }
analytics.logEvent('Notifying update', { analytics.logEvent('Notifying update', {
latestVersion latestVersion
}); })
return updateNotifier.notify(latestVersion, { return updateNotifier.notify(latestVersion, {
allowSleepUpdateCheck: currentReleaseType === release.RELEASE_TYPE.PRODUCTION allowSleepUpdateCheck: currentReleaseType === release.RELEASE_TYPE.PRODUCTION
}); })
}); })
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}); })
app.run(() => { app.run(() => {
store.subscribe(() => { store.subscribe(() => {
const currentFlashState = flashState.getFlashState(); const currentFlashState = flashState.getFlashState()
// There is usually a short time period between the `isFlashing()` // There is usually a short time period between the `isFlashing()`
// property being set, and the flashing actually starting, which // property being set, and the flashing actually starting, which
@ -169,18 +169,18 @@ app.run(() => {
// We use the presence of `.eta` to determine that the actual // We use the presence of `.eta` to determine that the actual
// writing started. // writing started.
if (!flashState.isFlashing() || !currentFlashState.eta) { if (!flashState.isFlashing() || !currentFlashState.eta) {
return; return
} }
analytics.logDebug([ analytics.logDebug([
`Progress (${currentFlashState.type}):`, `Progress (${currentFlashState.type}):`,
`${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s`, `${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s`,
`(eta ${currentFlashState.eta}s)` `(eta ${currentFlashState.eta}s)`
].join(' ')); ].join(' '))
windowProgress.set(currentFlashState.percentage); windowProgress.set(currentFlashState.percentage)
}); })
}); })
app.run(($timeout) => { app.run(($timeout) => {
driveScanner.on('drives', (drives) => { driveScanner.on('drives', (drives) => {
@ -189,41 +189,41 @@ app.run(($timeout) => {
// available drives list has changed, and incorrectly // available drives list has changed, and incorrectly
// keeps asking the user to "Connect a drive". // keeps asking the user to "Connect a drive".
$timeout(() => { $timeout(() => {
availableDrives.setDrives(drives); availableDrives.setDrives(drives)
}); })
}); })
driveScanner.on('error', (error) => { driveScanner.on('error', (error) => {
// Stop the drive scanning loop in case of errors, // Stop the drive scanning loop in case of errors,
// otherwise we risk presenting the same error over // otherwise we risk presenting the same error over
// and over again to the user, while also heavily // and over again to the user, while also heavily
// spamming our error reporting service. // spamming our error reporting service.
driveScanner.stop(); driveScanner.stop()
return exceptionReporter.report(error); return exceptionReporter.report(error)
}); })
driveScanner.start(); driveScanner.start()
}); })
app.run(($window) => { app.run(($window) => {
let popupExists = false; let popupExists = false
$window.addEventListener('beforeunload', (event) => { $window.addEventListener('beforeunload', (event) => {
if (!flashState.isFlashing() || popupExists) { if (!flashState.isFlashing() || popupExists) {
analytics.logEvent('Close application', { analytics.logEvent('Close application', {
isFlashing: flashState.isFlashing() isFlashing: flashState.isFlashing()
}); })
return; return
} }
// Don't close window while flashing // Don't close window while flashing
event.returnValue = false; event.returnValue = false
// Don't open any more popups // Don't open any more popups
popupExists = true; popupExists = true
analytics.logEvent('Close attempt while flashing'); analytics.logEvent('Close attempt while flashing')
osDialog.showWarning({ osDialog.showWarning({
confirmationLabel: 'Yes, quit', confirmationLabel: 'Yes, quit',
@ -234,45 +234,45 @@ app.run(($window) => {
if (confirmed) { if (confirmed) {
analytics.logEvent('Close confirmed while flashing', { analytics.logEvent('Close confirmed while flashing', {
uuid: flashState.getFlashUuid() uuid: flashState.getFlashUuid()
}); })
// This circumvents the 'beforeunload' event unlike // This circumvents the 'beforeunload' event unlike
// electron.remote.app.quit() which does not. // electron.remote.app.quit() which does not.
electron.remote.process.exit(EXIT_CODES.SUCCESS); electron.remote.process.exit(EXIT_CODES.SUCCESS)
} }
analytics.logEvent('Close rejected while flashing'); analytics.logEvent('Close rejected while flashing')
popupExists = false; popupExists = false
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}); })
}); })
app.run(($rootScope) => { app.run(($rootScope) => {
$rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => { $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => {
// Ignore first navigation // Ignore first navigation
if (!fromState.name) { if (!fromState.name) {
return; return
} }
analytics.logEvent('Navigate', { analytics.logEvent('Navigate', {
to: toState.name, to: toState.name,
from: fromState.name from: fromState.name
}); })
}); })
}); })
app.config(($urlRouterProvider) => { app.config(($urlRouterProvider) => {
$urlRouterProvider.otherwise('/main'); $urlRouterProvider.otherwise('/main')
}); })
app.config(($provide) => { app.config(($provide) => {
$provide.decorator('$exceptionHandler', ($delegate) => { $provide.decorator('$exceptionHandler', ($delegate) => {
return (exception, cause) => { return (exception, cause) => {
exceptionReporter.report(exception); exceptionReporter.report(exception)
$delegate(exception, cause); $delegate(exception, cause)
}; }
}); })
}); })
app.controller('HeaderController', function (OSOpenExternalService) { app.controller('HeaderController', function (OSOpenExternalService) {
/** /**
@ -288,19 +288,19 @@ app.controller('HeaderController', function (OSOpenExternalService) {
* HeaderController.openHelpPage(); * HeaderController.openHelpPage();
*/ */
this.openHelpPage = () => { this.openHelpPage = () => {
const DEFAULT_SUPPORT_URL = 'https://github.com/resin-io/etcher/blob/master/SUPPORT.md'; const DEFAULT_SUPPORT_URL = 'https://github.com/resin-io/etcher/blob/master/SUPPORT.md'
const supportUrl = selectionState.getImageSupportUrl() || DEFAULT_SUPPORT_URL; const supportUrl = selectionState.getImageSupportUrl() || DEFAULT_SUPPORT_URL
OSOpenExternalService.open(supportUrl); OSOpenExternalService.open(supportUrl)
}; }
}); })
app.controller('StateController', function ($rootScope, $scope) { app.controller('StateController', function ($rootScope, $scope) {
const unregisterStateChange = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => { const unregisterStateChange = $rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => {
this.previousName = fromState.name; this.previousName = fromState.name
this.currentName = toState.name; this.currentName = toState.name
}); })
$scope.$on('$destroy', unregisterStateChange); $scope.$on('$destroy', unregisterStateChange)
/** /**
* @summary Get the previous state name * @summary Get the previous state name
@ -314,7 +314,7 @@ app.controller('StateController', function ($rootScope, $scope) {
* console.log('We left the main screen!'); * console.log('We left the main screen!');
* } * }
*/ */
this.previousName = null; this.previousName = null
/** /**
* @summary Get the current state name * @summary Get the current state name
@ -328,5 +328,5 @@ app.controller('StateController', function ($rootScope, $scope) {
* console.log('We are on the main screen!'); * console.log('We are on the main screen!');
* } * }
*/ */
this.currentName = null; this.currentName = null
}); })

View File

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const angular = require('angular'); const angular = require('angular')
const _ = require('lodash'); const _ = require('lodash')
const messages = require('../../../../shared/messages'); const messages = require('../../../../shared/messages')
const constraints = require('../../../../shared/drive-constraints'); const constraints = require('../../../../shared/drive-constraints')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
const availableDrives = require('../../../../shared/models/available-drives'); const availableDrives = require('../../../../shared/models/available-drives')
const selectionState = require('../../../../shared/models/selection-state'); const selectionState = require('../../../../shared/models/selection-state')
module.exports = function ( module.exports = function (
$q, $q,
@ -34,14 +34,14 @@ module.exports = function (
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.state = selectionState; this.state = selectionState
/** /**
* @summary Static methods to check a drive's properties * @summary Static methods to check a drive's properties
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.constraints = constraints; this.constraints = constraints
/** /**
* @summary The drives model * @summary The drives model
@ -54,7 +54,7 @@ module.exports = function (
* this allows the property to be automatically updated * this allows the property to be automatically updated
* when `availableDrives` detects a change in the drives. * when `availableDrives` detects a change in the drives.
*/ */
this.drives = availableDrives; this.drives = availableDrives
/** /**
* @summary Determine if we can change a drive's selection state * @summary Determine if we can change a drive's selection state
@ -72,11 +72,11 @@ module.exports = function (
*/ */
const shouldChangeDriveSelectionState = (drive) => { const shouldChangeDriveSelectionState = (drive) => {
if (!constraints.isDriveValid(drive, selectionState.getImage())) { if (!constraints.isDriveValid(drive, selectionState.getImage())) {
return $q.resolve(false); return $q.resolve(false)
} }
if (constraints.isDriveSizeRecommended(drive, selectionState.getImage())) { if (constraints.isDriveSizeRecommended(drive, selectionState.getImage())) {
return $q.resolve(true); return $q.resolve(true)
} }
return WarningModalService.display({ return WarningModalService.display({
@ -88,8 +88,8 @@ module.exports = function (
}), }),
'Are you sure you want to continue?' 'Are you sure you want to continue?'
].join(' ') ].join(' ')
}); })
}; }
/** /**
* @summary Toggle a drive selection * @summary Toggle a drive selection
@ -110,14 +110,14 @@ module.exports = function (
analytics.logEvent('Toggle drive', { analytics.logEvent('Toggle drive', {
drive, drive,
previouslySelected: selectionState.isCurrentDrive(drive.device) previouslySelected: selectionState.isCurrentDrive(drive.device)
}); })
return shouldChangeDriveSelectionState(drive).then((canChangeDriveSelectionState) => { return shouldChangeDriveSelectionState(drive).then((canChangeDriveSelectionState) => {
if (canChangeDriveSelectionState) { if (canChangeDriveSelectionState) {
selectionState.toggleSetDrive(drive.device); selectionState.toggleSetDrive(drive.device)
}
})
} }
});
};
/** /**
* @summary Close the modal and resolve the selected drive * @summary Close the modal and resolve the selected drive
@ -128,17 +128,17 @@ module.exports = function (
* DriveSelectorController.closeModal(); * DriveSelectorController.closeModal();
*/ */
this.closeModal = () => { this.closeModal = () => {
const selectedDrive = selectionState.getDrive(); const selectedDrive = selectionState.getDrive()
// Sanity check to cover the case where a drive is selected, // Sanity check to cover the case where a drive is selected,
// the drive is then unplugged from the computer and the modal // the drive is then unplugged from the computer and the modal
// is resolved with a non-existent drive. // is resolved with a non-existent drive.
if (!selectedDrive || !_.includes(this.drives.getDrives(), selectedDrive)) { if (!selectedDrive || !_.includes(this.drives.getDrives(), selectedDrive)) {
$uibModalInstance.close(); $uibModalInstance.close()
} else { } else {
$uibModalInstance.close(selectedDrive); $uibModalInstance.close(selectedDrive)
}
} }
};
/** /**
* @summary Select a drive and close the modal * @summary Select a drive and close the modal
@ -158,14 +158,14 @@ module.exports = function (
this.selectDriveAndClose = (drive) => { this.selectDriveAndClose = (drive) => {
return shouldChangeDriveSelectionState(drive).then((canChangeDriveSelectionState) => { return shouldChangeDriveSelectionState(drive).then((canChangeDriveSelectionState) => {
if (canChangeDriveSelectionState) { if (canChangeDriveSelectionState) {
selectionState.setDrive(drive.device); selectionState.setDrive(drive.device)
analytics.logEvent('Drive selected (double click)'); analytics.logEvent('Drive selected (double click)')
this.closeModal(); this.closeModal()
}
})
} }
});
};
/** /**
* @summary Memoize ImmutableJS list reference * @summary Memoize ImmutableJS list reference
@ -193,41 +193,41 @@ module.exports = function (
* const memoizedFunction = memoizeImmutableListReference(getList); * const memoizedFunction = memoizeImmutableListReference(getList);
*/ */
this.memoizeImmutableListReference = (func) => { this.memoizeImmutableListReference = (func) => {
let previousTuples = []; let previousTuples = []
return (...restArgs) => { return (...restArgs) => {
let areArgsInTuple = false; let areArgsInTuple = false
let state = Reflect.apply(func, this, restArgs); let state = Reflect.apply(func, this, restArgs)
previousTuples = _.map(previousTuples, ([ oldArgs, oldState ]) => { previousTuples = _.map(previousTuples, ([ oldArgs, oldState ]) => {
if (angular.equals(oldArgs, restArgs)) { if (angular.equals(oldArgs, restArgs)) {
areArgsInTuple = true; areArgsInTuple = true
if (angular.equals(state, oldState)) { if (angular.equals(state, oldState)) {
// Use the previously memoized state for this argument // Use the previously memoized state for this argument
state = oldState; state = oldState
} }
// Update the tuple state // Update the tuple state
return [ oldArgs, state ]; return [ oldArgs, state ]
} }
// Return the tuple unchanged // Return the tuple unchanged
return [ oldArgs, oldState ]; return [ oldArgs, oldState ]
}); })
// Add the state associated with these args to be memoized // Add the state associated with these args to be memoized
if (!areArgsInTuple) { if (!areArgsInTuple) {
previousTuples.push([ restArgs, state ]); previousTuples.push([ restArgs, state ])
} }
return state; return state
}; }
}; }
this.getDrives = this.memoizeImmutableListReference(() => { this.getDrives = this.memoizeImmutableListReference(() => {
return this.drives.getDrives(); return this.drives.getDrives()
}); })
/** /**
* @summary Get a drive's compatibility status object(s) * @summary Get a drive's compatibility status object(s)
@ -249,6 +249,6 @@ module.exports = function (
* } * }
*/ */
this.getDriveStatuses = this.memoizeImmutableListReference((drive) => { this.getDriveStatuses = this.memoizeImmutableListReference((drive) => {
return this.constraints.getDriveImageCompatibilityStatuses(drive, this.state.getImage()); return this.constraints.getDriveImageCompatibilityStatuses(drive, this.state.getImage())
}); })
}; }

View File

@ -14,21 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Components.DriveSelector * @module Etcher.Components.DriveSelector
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Components.DriveSelector'; const MODULE_NAME = 'Etcher.Components.DriveSelector'
const DriveSelector = angular.module(MODULE_NAME, [ const DriveSelector = angular.module(MODULE_NAME, [
require('../modal/modal'), require('../modal/modal'),
require('../warning-modal/warning-modal'), require('../warning-modal/warning-modal'),
require('../../utils/byte-size/byte-size') require('../../utils/byte-size/byte-size')
]); ])
DriveSelector.controller('DriveSelectorController', require('./controllers/drive-selector')); DriveSelector.controller('DriveSelectorController', require('./controllers/drive-selector'))
DriveSelector.service('DriveSelectorService', require('./services/drive-selector')); DriveSelector.service('DriveSelectorService', require('./services/drive-selector'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
module.exports = function (ModalService, $q) { module.exports = function (ModalService, $q) {
let modal = null; let modal = null
/** /**
* @summary Open the drive selector widget * @summary Open the drive selector widget
@ -37,10 +37,10 @@ module.exports = function (ModalService, $q) {
template: './components/drive-selector/templates/drive-selector-modal.tpl.html', template: './components/drive-selector/templates/drive-selector-modal.tpl.html',
controller: 'DriveSelectorController as modal', controller: 'DriveSelectorController as modal',
size: 'drive-selector-modal' size: 'drive-selector-modal'
}); })
return modal.result; return modal.result
}; }
/** /**
* @summary Close the drive selector widget * @summary Close the drive selector widget
@ -55,11 +55,11 @@ module.exports = function (ModalService, $q) {
*/ */
this.close = () => { this.close = () => {
if (modal) { if (modal) {
return modal.close(); return modal.close()
} }
// Resolve `undefined` if the modal // Resolve `undefined` if the modal
// was already closed for consistency // was already closed for consistency
return $q.resolve(); return $q.resolve()
}; }
}; }

View File

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Components.FlashErrorModal * @module Etcher.Components.FlashErrorModal
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Components.FlashErrorModal'; const MODULE_NAME = 'Etcher.Components.FlashErrorModal'
const FlashErrorModal = angular.module(MODULE_NAME, [ const FlashErrorModal = angular.module(MODULE_NAME, [
require('../warning-modal/warning-modal') require('../warning-modal/warning-modal')
]); ])
FlashErrorModal.service('FlashErrorModalService', require('./services/flash-error-modal')); FlashErrorModal.service('FlashErrorModalService', require('./services/flash-error-modal'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const flashState = require('../../../../shared/models/flash-state'); const flashState = require('../../../../shared/models/flash-state')
const selectionState = require('../../../../shared/models/selection-state'); const selectionState = require('../../../../shared/models/selection-state')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
module.exports = function (WarningModalService) { module.exports = function (WarningModalService) {
/** /**
@ -37,13 +37,13 @@ module.exports = function (WarningModalService) {
confirmationLabel: 'Retry', confirmationLabel: 'Retry',
description: message description: message
}).then((confirmed) => { }).then((confirmed) => {
flashState.resetState(); flashState.resetState()
if (confirmed) { if (confirmed) {
analytics.logEvent('Restart after failure'); analytics.logEvent('Restart after failure')
} else { } else {
selectionState.clear(); selectionState.clear()
} }
}); })
}; }
}; }

View File

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Components.Modal * @module Etcher.Components.Modal
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Components.Modal'; const MODULE_NAME = 'Etcher.Components.Modal'
const Modal = angular.module(MODULE_NAME, [ const Modal = angular.module(MODULE_NAME, [
require('angular-ui-bootstrap') require('angular-ui-bootstrap')
]); ])
Modal.service('ModalService', require('./services/modal')); Modal.service('ModalService', require('./services/modal'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
module.exports = function ($uibModal, $q) { module.exports = function ($uibModal, $q) {
/** /**
@ -41,11 +41,11 @@ module.exports = function ($uibModal, $q) {
this.open = (options = {}) => { this.open = (options = {}) => {
_.defaults(options, { _.defaults(options, {
size: 'sm' size: 'sm'
}); })
analytics.logEvent('Open modal', { analytics.logEvent('Open modal', {
template: options.template template: options.template
}); })
const modal = $uibModal.open({ const modal = $uibModal.open({
animation: true, animation: true,
@ -53,7 +53,7 @@ module.exports = function ($uibModal, $q) {
controller: options.controller, controller: options.controller,
size: options.size, size: options.size,
resolve: options.resolve resolve: options.resolve
}); })
return { return {
close: modal.close, close: modal.close,
@ -61,26 +61,26 @@ module.exports = function ($uibModal, $q) {
modal.result.then((value) => { modal.result.then((value) => {
analytics.logEvent('Modal accepted', { analytics.logEvent('Modal accepted', {
value value
}); })
resolve(value); resolve(value)
}).catch((error) => { }).catch((error) => {
// Bootstrap doesn't 'resolve' these but cancels the dialog // Bootstrap doesn't 'resolve' these but cancels the dialog
if (error === 'escape key press' || error === 'backdrop click') { if (error === 'escape key press' || error === 'backdrop click') {
analytics.logEvent('Modal rejected', { analytics.logEvent('Modal rejected', {
method: error method: error
}); })
return resolve(); return resolve()
} }
analytics.logEvent('Modal rejected', { analytics.logEvent('Modal rejected', {
value: error value: error
});
return reject(error);
});
}) })
};
}; return reject(error)
}; })
})
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @summary ProgressButton directive * @summary ProgressButton directive
@ -40,5 +40,5 @@ module.exports = () => {
percentage: '=', percentage: '=',
striped: '@' striped: '@'
} }
}; }
}; }

View File

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Components.ProgressButton * @module Etcher.Components.ProgressButton
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Components.ProgressButton'; const MODULE_NAME = 'Etcher.Components.ProgressButton'
const ProgressButton = angular.module(MODULE_NAME, []); const ProgressButton = angular.module(MODULE_NAME, [])
ProgressButton.directive('progressButton', require('./directives/progress-button')); ProgressButton.directive('progressButton', require('./directives/progress-button'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,22 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/* eslint-disable jsdoc/require-example */ /* eslint-disable jsdoc/require-example */
const _ = require('lodash'); const _ = require('lodash')
const electron = require('electron'); const electron = require('electron')
const angular = require('angular'); const angular = require('angular')
const react = require('react'); const react = require('react')
const propTypes = require('prop-types'); const propTypes = require('prop-types')
const react2angular = require('react2angular').react2angular; const react2angular = require('react2angular').react2angular
const analytics = require('../modules/analytics'); const analytics = require('../modules/analytics')
const packageJSON = require('../../../package.json'); const packageJSON = require('../../../package.json')
const robot = require('../../shared/robot'); const robot = require('../../shared/robot')
const MODULE_NAME = 'Etcher.Components.SafeWebview'; const MODULE_NAME = 'Etcher.Components.SafeWebview'
const angularSafeWebview = angular.module(MODULE_NAME, []); const angularSafeWebview = angular.module(MODULE_NAME, [])
/** /**
* @summary Electron session identifier * @summary Electron session identifier
@ -37,7 +37,7 @@ const angularSafeWebview = angular.module(MODULE_NAME, []);
* @private * @private
* @type {String} * @type {String}
*/ */
const ELECTRON_SESSION = 'persist:success-banner'; const ELECTRON_SESSION = 'persist:success-banner'
/** /**
* @summary Etcher version search-parameter key * @summary Etcher version search-parameter key
@ -45,7 +45,7 @@ const ELECTRON_SESSION = 'persist:success-banner';
* @private * @private
* @type {String} * @type {String}
*/ */
const ETCHER_VERSION_PARAM = 'etcher-version'; const ETCHER_VERSION_PARAM = 'etcher-version'
/** /**
* @summary API version search-parameter key * @summary API version search-parameter key
@ -53,7 +53,7 @@ const ETCHER_VERSION_PARAM = 'etcher-version';
* @private * @private
* @type {String} * @type {String}
*/ */
const API_VERSION_PARAM = 'api-version'; const API_VERSION_PARAM = 'api-version'
/** /**
* @summary Webview API version * @summary Webview API version
@ -67,7 +67,7 @@ const API_VERSION_PARAM = 'api-version';
* This version number is exposed to the banner such that it can determine what * This version number is exposed to the banner such that it can determine what
* features are safe to utilize. * features are safe to utilize.
*/ */
const API_VERSION = 1; const API_VERSION = 1
/** /**
* @summary Webviews that hide/show depending on the HTTP status returned * @summary Webviews that hide/show depending on the HTTP status returned
@ -82,37 +82,37 @@ class SafeWebview extends react.PureComponent {
* @param {Object} props - React element properties * @param {Object} props - React element properties
*/ */
constructor (props) { constructor (props) {
super(props); super(props)
this.state = { this.state = {
shouldShow: true shouldShow: true
}; }
const url = new window.URL(props.src); const url = new window.URL(props.src)
// We set the version GET parameters here. // We set the version GET parameters here.
url.searchParams.set(ETCHER_VERSION_PARAM, packageJSON.version); url.searchParams.set(ETCHER_VERSION_PARAM, packageJSON.version)
url.searchParams.set(API_VERSION_PARAM, API_VERSION); url.searchParams.set(API_VERSION_PARAM, API_VERSION)
this.entryHref = url.href; this.entryHref = url.href
// Events steal 'this' // Events steal 'this'
this.didFailLoad = _.bind(this.didFailLoad, this); this.didFailLoad = _.bind(this.didFailLoad, this)
this.didGetResponseDetails = _.bind(this.didGetResponseDetails, this); this.didGetResponseDetails = _.bind(this.didGetResponseDetails, this)
this.eventTuples = [ this.eventTuples = [
[ 'did-fail-load', this.didFailLoad ], [ 'did-fail-load', this.didFailLoad ],
[ 'did-get-response-details', this.didGetResponseDetails ], [ 'did-get-response-details', this.didGetResponseDetails ],
[ 'new-window', this.constructor.newWindow ], [ 'new-window', this.constructor.newWindow ],
[ 'console-message', this.constructor.consoleMessage ] [ 'console-message', this.constructor.consoleMessage ]
]; ]
// Make a persistent electron session for the webview // Make a persistent electron session for the webview
electron.remote.session.fromPartition(ELECTRON_SESSION, { electron.remote.session.fromPartition(ELECTRON_SESSION, {
// Disable the cache for the session such that new content shows up when refreshing // Disable the cache for the session such that new content shows up when refreshing
cache: false cache: false
}); })
} }
/** /**
@ -126,7 +126,7 @@ class SafeWebview extends react.PureComponent {
width: this.state.shouldShow ? null : '0', width: this.state.shouldShow ? null : '0',
height: this.state.shouldShow ? null : '0' height: this.state.shouldShow ? null : '0'
} }
}, []); }, [])
} }
/** /**
@ -135,15 +135,15 @@ class SafeWebview extends react.PureComponent {
componentDidMount () { componentDidMount () {
// Events React is unaware of have to be handled manually // Events React is unaware of have to be handled manually
_.map(this.eventTuples, (tuple) => { _.map(this.eventTuples, (tuple) => {
this.refs.webview.addEventListener(...tuple); this.refs.webview.addEventListener(...tuple)
}); })
// Use the 'success-banner' session // Use the 'success-banner' session
this.refs.webview.partition = ELECTRON_SESSION; this.refs.webview.partition = ELECTRON_SESSION
// It's important that this comes after the partition setting, otherwise it will // It's important that this comes after the partition setting, otherwise it will
// use another session and we can't change it without destroying the element again // use another session and we can't change it without destroying the element again
this.refs.webview.src = this.entryHref; this.refs.webview.src = this.entryHref
} }
/** /**
@ -152,8 +152,8 @@ class SafeWebview extends react.PureComponent {
componentWillUnmount () { componentWillUnmount () {
// Events that React is unaware of have to be handled manually // Events that React is unaware of have to be handled manually
_.map(this.eventTuples, (tuple) => { _.map(this.eventTuples, (tuple) => {
this.refs.webview.removeEventListener(...tuple); this.refs.webview.removeEventListener(...tuple)
}); })
} }
/** /**
@ -166,14 +166,14 @@ class SafeWebview extends react.PureComponent {
// because reload interferes with 'src' setting, resetting the 'src' attribute // because reload interferes with 'src' setting, resetting the 'src' attribute
// to what it was was just prior. // to what it was was just prior.
if (this.refs.webview.src === this.entryHref) { if (this.refs.webview.src === this.entryHref) {
this.refs.webview.reload(); this.refs.webview.reload()
} else { } else {
this.refs.webview.src = this.entryHref; this.refs.webview.src = this.entryHref
} }
this.setState({ this.setState({
shouldShow: true shouldShow: true
}); })
} }
} }
@ -183,7 +183,7 @@ class SafeWebview extends react.PureComponent {
didFailLoad () { didFailLoad () {
this.setState({ this.setState({
shouldShow: false shouldShow: false
}); })
} }
/** /**
@ -191,12 +191,12 @@ class SafeWebview extends react.PureComponent {
* @param {Event} event - Event object * @param {Event} event - Event object
*/ */
didGetResponseDetails (event) { didGetResponseDetails (event) {
const HTTP_OK = 200; const HTTP_OK = 200
const HTTP_ERR = 400; const HTTP_ERR = 400
this.setState({ this.setState({
shouldShow: event.httpResponseCode >= HTTP_OK && event.httpResponseCode < HTTP_ERR shouldShow: event.httpResponseCode >= HTTP_OK && event.httpResponseCode < HTTP_ERR
}); })
} }
/** /**
@ -204,13 +204,13 @@ class SafeWebview extends react.PureComponent {
* @param {Event} event - event object * @param {Event} event - event object
*/ */
static newWindow (event) { static newWindow (event) {
const url = new window.URL(event.url); const url = new window.URL(event.url)
if (_.every([ if (_.every([
url.protocol === 'http:' || url.protocol === 'https:', url.protocol === 'http:' || url.protocol === 'https:',
event.disposition === 'foreground-tab' event.disposition === 'foreground-tab'
])) { ])) {
electron.shell.openExternal(url.href); electron.shell.openExternal(url.href)
} }
} }
@ -233,15 +233,15 @@ class SafeWebview extends react.PureComponent {
*/ */
static consoleMessage (event) { static consoleMessage (event) {
if (!robot.isMessage(event.message)) { if (!robot.isMessage(event.message)) {
return; return
} }
const message = robot.parseMessage(event.message); const message = robot.parseMessage(event.message)
if (robot.getCommand(message) === robot.COMMAND.LOG) { if (robot.getCommand(message) === robot.COMMAND.LOG) {
analytics.logEvent(robot.getData(message)); analytics.logEvent(robot.getData(message))
} else if (robot.getCommand(message) === robot.COMMAND.ERROR) { } else if (robot.getCommand(message) === robot.COMMAND.ERROR) {
analytics.logException(robot.getData(message)); analytics.logException(robot.getData(message))
} }
} }
} }
@ -258,8 +258,8 @@ SafeWebview.propTypes = {
*/ */
refreshNow: propTypes.bool refreshNow: propTypes.bool
}; }
angularSafeWebview.component('safeWebview', react2angular(SafeWebview)); angularSafeWebview.component('safeWebview', react2angular(SafeWebview))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/* eslint-disable jsdoc/require-example */ /* eslint-disable jsdoc/require-example */
@ -22,18 +22,18 @@
* @module Etcher.Components.SVGIcon * @module Etcher.Components.SVGIcon
*/ */
const _ = require('lodash'); const _ = require('lodash')
const angular = require('angular'); const angular = require('angular')
const react = require('react'); const react = require('react')
const propTypes = require('prop-types'); const propTypes = require('prop-types')
const react2angular = require('react2angular').react2angular; const react2angular = require('react2angular').react2angular
const path = require('path'); const path = require('path')
const fs = require('fs'); const fs = require('fs')
const MODULE_NAME = 'Etcher.Components.SVGIcon'; const MODULE_NAME = 'Etcher.Components.SVGIcon'
const angularSVGIcon = angular.module(MODULE_NAME, []); const angularSVGIcon = angular.module(MODULE_NAME, [])
const DEFAULT_SIZE = '40px'; const DEFAULT_SIZE = '40px'
/** /**
* @summary SVG element that takes both filepaths and file contents * @summary SVG element that takes both filepaths and file contents
@ -50,27 +50,27 @@ class SVGIcon extends react.Component {
// relative to *this directory*. // relative to *this directory*.
// TODO: There might be a way to compute the path // TODO: There might be a way to compute the path
// relatively to the `index.html`. // relatively to the `index.html`.
const imagePath = path.join(__dirname, this.props.path); const imagePath = path.join(__dirname, this.props.path)
let contents = ''; let contents = ''
if (_.startsWith(this.props.path, '<')) { if (_.startsWith(this.props.path, '<')) {
contents = this.props.path; contents = this.props.path
} else { } else {
contents = fs.readFileSync(imagePath, { contents = fs.readFileSync(imagePath, {
encoding: 'utf8' encoding: 'utf8'
}); })
} }
const width = this.props.width || DEFAULT_SIZE; const width = this.props.width || DEFAULT_SIZE
const height = this.props.height || DEFAULT_SIZE; const height = this.props.height || DEFAULT_SIZE
const parser = new window.DOMParser(); const parser = new window.DOMParser()
const doc = parser.parseFromString(contents, 'image/svg+xml'); const doc = parser.parseFromString(contents, 'image/svg+xml')
const svg = doc.querySelector('svg'); const svg = doc.querySelector('svg')
const img = document.createElement('img'); const img = document.createElement('img')
img.src = `data:image/svg+xml,${encodeURIComponent(svg.outerHTML)}`; img.src = `data:image/svg+xml,${encodeURIComponent(svg.outerHTML)}`
return react.createElement('div', { return react.createElement('div', {
className: 'svg-icon', className: 'svg-icon',
@ -84,7 +84,7 @@ class SVGIcon extends react.Component {
dangerouslySetInnerHTML: { dangerouslySetInnerHTML: {
__html: img.outerHTML __html: img.outerHTML
} }
}); })
} }
/** /**
@ -93,7 +93,7 @@ class SVGIcon extends react.Component {
*/ */
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
// This will update the element if the properties change // This will update the element if the properties change
this.setState(nextProps); this.setState(nextProps)
} }
} }
@ -119,7 +119,7 @@ SVGIcon.propTypes = {
*/ */
disabled: propTypes.bool disabled: propTypes.bool
}; }
angularSVGIcon.component('svgIcon', react2angular(SVGIcon)); angularSVGIcon.component('svgIcon', react2angular(SVGIcon))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
module.exports = function ($uibModalInstance, tooltipData) { module.exports = function ($uibModalInstance, tooltipData) {
/** /**
@ -22,7 +22,7 @@ module.exports = function ($uibModalInstance, tooltipData) {
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.data = tooltipData; this.data = tooltipData
/** /**
* @summary Close the modal * @summary Close the modal
@ -33,6 +33,6 @@ module.exports = function ($uibModalInstance, tooltipData) {
* TooltipModalController.closeModal(); * TooltipModalController.closeModal();
*/ */
this.closeModal = () => { this.closeModal = () => {
$uibModalInstance.dismiss(); $uibModalInstance.dismiss()
}; }
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
module.exports = function (ModalService) { module.exports = function (ModalService) {
/** /**
@ -43,6 +43,6 @@ module.exports = function (ModalService) {
resolve: { resolve: {
tooltipData: _.constant(options) tooltipData: _.constant(options)
} }
}).result; }).result
}; }
}; }

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Components.TooltipModal * @module Etcher.Components.TooltipModal
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Components.TooltipModal'; const MODULE_NAME = 'Etcher.Components.TooltipModal'
const TooltipModal = angular.module(MODULE_NAME, [ const TooltipModal = angular.module(MODULE_NAME, [
require('../modal/modal') require('../modal/modal')
]); ])
TooltipModal.controller('TooltipModalController', require('./controllers/tooltip-modal')); TooltipModal.controller('TooltipModalController', require('./controllers/tooltip-modal'))
TooltipModal.service('TooltipModalService', require('./services/tooltip-modal')); TooltipModal.service('TooltipModalService', require('./services/tooltip-modal'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,16 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const electron = require('electron'); const electron = require('electron')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const _ = require('lodash'); const _ = require('lodash')
const settings = require('../models/settings'); const settings = require('../models/settings')
const analytics = require('../modules/analytics'); const analytics = require('../modules/analytics')
const units = require('../../shared/units'); const units = require('../../shared/units')
const release = require('../../shared/release'); const release = require('../../shared/release')
const packageJSON = require('../../../package.json'); const packageJSON = require('../../../package.json')
/** /**
* @summary The number of days the update notifier can be put to sleep * @summary The number of days the update notifier can be put to sleep
@ -31,7 +31,7 @@ const packageJSON = require('../../../package.json');
* @private * @private
* @type {Number} * @type {Number}
*/ */
exports.UPDATE_NOTIFIER_SLEEP_DAYS = packageJSON.updates.sleepDays; exports.UPDATE_NOTIFIER_SLEEP_DAYS = packageJSON.updates.sleepDays
/** /**
* @summary The current Electron browser window * @summary The current Electron browser window
@ -39,7 +39,7 @@ exports.UPDATE_NOTIFIER_SLEEP_DAYS = packageJSON.updates.sleepDays;
* @private * @private
* @type {Object} * @type {Object}
*/ */
const currentWindow = electron.remote.getCurrentWindow(); const currentWindow = electron.remote.getCurrentWindow()
/** /**
* @summary Determine if it's time to check for updates * @summary Determine if it's time to check for updates
@ -64,18 +64,18 @@ const currentWindow = electron.remote.getCurrentWindow();
exports.shouldCheckForUpdates = (options) => { exports.shouldCheckForUpdates = (options) => {
_.defaults(options, { _.defaults(options, {
lastSleptUpdateNotifierVersion: options.currentVersion lastSleptUpdateNotifierVersion: options.currentVersion
}); })
if (_.some([ if (_.some([
!options.lastSleptUpdateNotifier, !options.lastSleptUpdateNotifier,
release.getReleaseType(options.currentVersion) !== release.RELEASE_TYPE.PRODUCTION, release.getReleaseType(options.currentVersion) !== release.RELEASE_TYPE.PRODUCTION,
options.currentVersion !== options.lastSleptUpdateNotifierVersion options.currentVersion !== options.lastSleptUpdateNotifierVersion
])) { ])) {
return true; return true
} }
return Date.now() - options.lastSleptUpdateNotifier > units.daysToMilliseconds(this.UPDATE_NOTIFIER_SLEEP_DAYS); return Date.now() - options.lastSleptUpdateNotifier > units.daysToMilliseconds(this.UPDATE_NOTIFIER_SLEEP_DAYS)
}; }
/** /**
* @summary Open the update notifier widget * @summary Open the update notifier widget
@ -96,10 +96,10 @@ exports.notify = (version, options = {}) => {
const BUTTONS = [ const BUTTONS = [
'Download', 'Download',
'Skip' 'Skip'
]; ]
const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, _.first(BUTTONS)); const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, _.first(BUTTONS))
const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, _.last(BUTTONS)); const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, _.last(BUTTONS))
const dialogOptions = { const dialogOptions = {
type: 'info', type: 'info',
@ -108,13 +108,13 @@ exports.notify = (version, options = {}) => {
cancelId: BUTTON_REJECTION_INDEX, cancelId: BUTTON_REJECTION_INDEX,
title: 'New Update Available!', title: 'New Update Available!',
message: `Etcher ${version} is available for download` message: `Etcher ${version} is available for download`
}; }
if (_.get(options, [ 'allowSleepUpdateCheck' ], true)) { if (_.get(options, [ 'allowSleepUpdateCheck' ], true)) {
_.merge(dialogOptions, { _.merge(dialogOptions, {
checkboxLabel: `Remind me again in ${this.UPDATE_NOTIFIER_SLEEP_DAYS} days`, checkboxLabel: `Remind me again in ${this.UPDATE_NOTIFIER_SLEEP_DAYS} days`,
checkboxChecked: false checkboxChecked: false
}); })
} }
return new Bluebird((resolve) => { return new Bluebird((resolve) => {
@ -122,8 +122,8 @@ exports.notify = (version, options = {}) => {
return resolve({ return resolve({
agreed: response === BUTTON_CONFIRMATION_INDEX, agreed: response === BUTTON_CONFIRMATION_INDEX,
sleepUpdateCheck: checkboxChecked || false sleepUpdateCheck: checkboxChecked || false
}); })
}); })
}).tap((results) => { }).tap((results) => {
// Only update the last slept update timestamp if the // Only update the last slept update timestamp if the
// user ticked the "Remind me again in ..." checkbox, // user ticked the "Remind me again in ..." checkbox,
@ -132,20 +132,20 @@ exports.notify = (version, options = {}) => {
return Bluebird.all([ return Bluebird.all([
settings.set('lastSleptUpdateNotifier', Date.now()), settings.set('lastSleptUpdateNotifier', Date.now()),
settings.set('lastSleptUpdateNotifierVersion', packageJSON.version) settings.set('lastSleptUpdateNotifierVersion', packageJSON.version)
]); ])
} }
return Bluebird.resolve(); return Bluebird.resolve()
}).then((results) => { }).then((results) => {
analytics.logEvent('Close update modal', { analytics.logEvent('Close update modal', {
sleepUpdateCheck: results.sleepUpdateCheck, sleepUpdateCheck: results.sleepUpdateCheck,
notifyVersion: version, notifyVersion: version,
currentVersion: packageJSON.version, currentVersion: packageJSON.version,
agreed: results.agreed agreed: results.agreed
}); })
if (results.agreed) { if (results.agreed) {
electron.shell.openExternal('https://etcher.io?ref=etcher_update'); electron.shell.openExternal('https://etcher.io?ref=etcher_update')
} }
}); })
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
module.exports = function ($uibModalInstance, options) { module.exports = function ($uibModalInstance, options) {
/** /**
@ -22,7 +22,7 @@ module.exports = function ($uibModalInstance, options) {
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.options = options; this.options = options
/** /**
* @summary Reject the warning prompt * @summary Reject the warning prompt
@ -33,8 +33,8 @@ module.exports = function ($uibModalInstance, options) {
* WarningModalController.reject(); * WarningModalController.reject();
*/ */
this.reject = () => { this.reject = () => {
$uibModalInstance.close(false); $uibModalInstance.close(false)
}; }
/** /**
* @summary Accept the warning prompt * @summary Accept the warning prompt
@ -45,6 +45,6 @@ module.exports = function ($uibModalInstance, options) {
* WarningModalController.accept(); * WarningModalController.accept();
*/ */
this.accept = () => { this.accept = () => {
$uibModalInstance.close(true); $uibModalInstance.close(true)
}; }
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
module.exports = function ($sce, ModalService) { module.exports = function ($sce, ModalService) {
/** /**
@ -38,7 +38,7 @@ module.exports = function ($sce, ModalService) {
* }); * });
*/ */
this.display = (options = {}) => { this.display = (options = {}) => {
options.description = $sce.trustAsHtml(options.description); options.description = $sce.trustAsHtml(options.description)
return ModalService.open({ return ModalService.open({
template: './components/warning-modal/templates/warning-modal.tpl.html', template: './components/warning-modal/templates/warning-modal.tpl.html',
controller: 'WarningModalController as modal', controller: 'WarningModalController as modal',
@ -46,6 +46,6 @@ module.exports = function ($sce, ModalService) {
resolve: { resolve: {
options: _.constant(options) options: _.constant(options)
} }
}).result; }).result
}; }
}; }

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Components.WarningModal * @module Etcher.Components.WarningModal
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Components.WarningModal'; const MODULE_NAME = 'Etcher.Components.WarningModal'
const WarningModal = angular.module(MODULE_NAME, [ const WarningModal = angular.module(MODULE_NAME, [
require('../modal/modal') require('../modal/modal')
]); ])
WarningModal.controller('WarningModalController', require('./controllers/warning-modal')); WarningModal.controller('WarningModalController', require('./controllers/warning-modal'))
WarningModal.service('WarningModalService', require('./services/warning-modal')); WarningModal.service('WarningModalService', require('./services/warning-modal'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const electron = require('electron'); const electron = require('electron')
const _ = require('lodash'); const _ = require('lodash')
const path = require('path'); const path = require('path')
const EXIT_CODES = require('../shared/exit-codes'); const EXIT_CODES = require('../shared/exit-codes')
let mainWindow = null; let mainWindow = null
// Enable drivelist debugging information // Enable drivelist debugging information
// See https://github.com/resin-io-modules/drivelist // See https://github.com/resin-io-modules/drivelist
process.env.DRIVELIST_DEBUG = 1; process.env.DRIVELIST_DEBUG = 1
electron.app.on('window-all-closed', electron.app.quit); electron.app.on('window-all-closed', electron.app.quit)
// Sending a `SIGINT` (e.g: Ctrl-C) to an Electron app that registers // Sending a `SIGINT` (e.g: Ctrl-C) to an Electron app that registers
// a `beforeunload` window event handler results in a disconnected white // a `beforeunload` window event handler results in a disconnected white
@ -35,12 +35,12 @@ electron.app.on('window-all-closed', electron.app.quit);
// make use of it to ensure the browser window is completely destroyed. // make use of it to ensure the browser window is completely destroyed.
// See https://github.com/electron/electron/issues/5273 // See https://github.com/electron/electron/issues/5273
electron.app.on('before-quit', () => { electron.app.on('before-quit', () => {
process.exit(EXIT_CODES.SUCCESS); process.exit(EXIT_CODES.SUCCESS)
}); })
electron.app.on('ready', () => { electron.app.on('ready', () => {
// No menu bar // No menu bar
electron.Menu.setApplicationMenu(null); electron.Menu.setApplicationMenu(null)
mainWindow = new electron.BrowserWindow({ mainWindow = new electron.BrowserWindow({
width: 800, width: 800,
@ -51,14 +51,14 @@ electron.app.on('ready', () => {
fullscreen: false, fullscreen: false,
titleBarStyle: 'hidden-inset', titleBarStyle: 'hidden-inset',
icon: path.join(__dirname, '..', '..', 'assets', 'icon.png') icon: path.join(__dirname, '..', '..', 'assets', 'icon.png')
}); })
// Prevent flash of white when starting the application // Prevent flash of white when starting the application
mainWindow.on('ready-to-show', mainWindow.show); mainWindow.on('ready-to-show', mainWindow.show)
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
mainWindow = null; mainWindow = null
}); })
// For some reason, Electron shortcuts are registered // For some reason, Electron shortcuts are registered
// globally, which means that the app listers for shorcuts // globally, which means that the app listers for shorcuts
@ -72,21 +72,21 @@ electron.app.on('ready', () => {
electron.globalShortcut.register('CmdOrCtrl+Alt+I', () => { electron.globalShortcut.register('CmdOrCtrl+Alt+I', () => {
mainWindow.webContents.openDevTools({ mainWindow.webContents.openDevTools({
mode: 'detach' mode: 'detach'
}); })
}); })
// Disable refreshing the browser window // Disable refreshing the browser window
// This is supposed to be handled by the `will-navigate` // This is supposed to be handled by the `will-navigate`
// event, however there seems to be an issue where such // event, however there seems to be an issue where such
// event is not fired in macOS // event is not fired in macOS
// See: https://github.com/electron/electron/issues/8841 // See: https://github.com/electron/electron/issues/8841
electron.globalShortcut.register('CmdOrCtrl+R', _.noop); electron.globalShortcut.register('CmdOrCtrl+R', _.noop)
electron.globalShortcut.register('F5', _.noop); electron.globalShortcut.register('F5', _.noop)
}); })
mainWindow.on('blur', () => { mainWindow.on('blur', () => {
electron.globalShortcut.unregisterAll(); electron.globalShortcut.unregisterAll()
}); })
// Prevent the user from being allowed to zoom-in the application. // Prevent the user from being allowed to zoom-in the application.
// //
@ -96,14 +96,14 @@ electron.app.on('ready', () => {
// electron desktop experience fixes in this file. // electron desktop experience fixes in this file.
// //
// See https://github.com/electron/electron/issues/3609 // See https://github.com/electron/electron/issues/3609
mainWindow.webContents.executeJavaScript('require(\'electron\').webFrame.setZoomLevelLimits(1, 1);'); mainWindow.webContents.executeJavaScript('require(\'electron\').webFrame.setZoomLevelLimits(1, 1);')
// Prevent external resources from being loaded (like images) // Prevent external resources from being loaded (like images)
// when dropping them on the WebView. // when dropping them on the WebView.
// See https://github.com/electron/electron/issues/5919 // See https://github.com/electron/electron/issues/5919
mainWindow.webContents.on('will-navigate', (event) => { mainWindow.webContents.on('will-navigate', (event) => {
event.preventDefault(); event.preventDefault()
}); })
mainWindow.loadURL(`file://${path.join(__dirname, 'index.html')}`); mainWindow.loadURL(`file://${path.join(__dirname, 'index.html')}`)
}); })

View File

@ -14,16 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
/** /**
* @summary Local storage settings key * @summary Local storage settings key
* @constant * @constant
* @type {String} * @type {String}
*/ */
const LOCAL_STORAGE_SETTINGS_KEY = 'etcher-settings'; const LOCAL_STORAGE_SETTINGS_KEY = 'etcher-settings'
/** /**
* @summary Read all local settings * @summary Read all local settings
@ -40,9 +40,9 @@ const LOCAL_STORAGE_SETTINGS_KEY = 'etcher-settings';
*/ */
exports.readAll = () => { exports.readAll = () => {
return Bluebird.try(() => { return Bluebird.try(() => {
return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SETTINGS_KEY)) || {}; return JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_SETTINGS_KEY)) || {}
}); })
}; }
/** /**
* @summary Write local settings * @summary Write local settings
@ -60,11 +60,11 @@ exports.readAll = () => {
* }); * });
*/ */
exports.writeAll = (settings) => { exports.writeAll = (settings) => {
const INDENTATION_SPACES = 2; const INDENTATION_SPACES = 2
return Bluebird.try(() => { return Bluebird.try(() => {
window.localStorage.setItem(LOCAL_STORAGE_SETTINGS_KEY, JSON.stringify(settings, null, INDENTATION_SPACES)); window.localStorage.setItem(LOCAL_STORAGE_SETTINGS_KEY, JSON.stringify(settings, null, INDENTATION_SPACES))
}); })
}; }
/** /**
* @summary Clear the local settings * @summary Clear the local settings
@ -83,6 +83,6 @@ exports.writeAll = (settings) => {
*/ */
exports.clear = () => { exports.clear = () => {
return Bluebird.try(() => { return Bluebird.try(() => {
window.localStorage.removeItem(LOCAL_STORAGE_SETTINGS_KEY); window.localStorage.removeItem(LOCAL_STORAGE_SETTINGS_KEY)
}); })
}; }

View File

@ -14,17 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Models.Settings * @module Etcher.Models.Settings
*/ */
const _ = require('lodash'); const _ = require('lodash')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const localSettings = require('./local-settings'); const localSettings = require('./local-settings')
const store = require('../../shared/store'); const store = require('../../shared/store')
const errors = require('../../shared/errors'); const errors = require('../../shared/errors')
/** /**
* @summary Set a settings object * @summary Set a settings object
@ -48,13 +48,13 @@ const errors = require('../../shared/errors');
* }); * });
*/ */
const setSettingsObject = (settings) => { const setSettingsObject = (settings) => {
const currentSettings = exports.getAll(); const currentSettings = exports.getAll()
return Bluebird.try(() => { return Bluebird.try(() => {
store.dispatch({ store.dispatch({
type: store.Actions.SET_SETTINGS, type: store.Actions.SET_SETTINGS,
data: settings data: settings
}); })
}).then(() => { }).then(() => {
// Revert the application state if writing the data // Revert the application state if writing the data
// to the local machine was not successful // to the local machine was not successful
@ -62,19 +62,19 @@ const setSettingsObject = (settings) => {
store.dispatch({ store.dispatch({
type: store.Actions.SET_SETTINGS, type: store.Actions.SET_SETTINGS,
data: currentSettings data: currentSettings
}); })
throw error; throw error
}); })
}); })
}; }
/** /**
* @summary Default settings * @summary Default settings
* @constant * @constant
* @type {Object} * @type {Object}
*/ */
const DEFAULT_SETTINGS = store.Defaults.get('settings').toJS(); const DEFAULT_SETTINGS = store.Defaults.get('settings').toJS()
/** /**
* @summary Reset settings to their default values * @summary Reset settings to their default values
@ -89,8 +89,8 @@ const DEFAULT_SETTINGS = store.Defaults.get('settings').toJS();
* }); * });
*/ */
exports.reset = () => { exports.reset = () => {
return setSettingsObject(DEFAULT_SETTINGS); return setSettingsObject(DEFAULT_SETTINGS)
}; }
/** /**
* @summary Extend the current settings * @summary Extend the current settings
@ -111,11 +111,11 @@ exports.assign = (settings) => {
if (_.isNil(settings)) { if (_.isNil(settings)) {
return Bluebird.reject(errors.createError({ return Bluebird.reject(errors.createError({
title: 'Missing settings' title: 'Missing settings'
})); }))
} }
return setSettingsObject(_.assign(exports.getAll(), settings)); return setSettingsObject(_.assign(exports.getAll(), settings))
}; }
/** /**
* @summary Extend the application state with the local settings * @summary Extend the application state with the local settings
@ -130,8 +130,8 @@ exports.assign = (settings) => {
* }); * });
*/ */
exports.load = () => { exports.load = () => {
return localSettings.readAll().then(exports.assign); return localSettings.readAll().then(exports.assign)
}; }
/** /**
* @summary Set a setting value * @summary Set a setting value
@ -151,19 +151,19 @@ exports.set = (key, value) => {
if (_.isNil(key)) { if (_.isNil(key)) {
return Bluebird.reject(errors.createError({ return Bluebird.reject(errors.createError({
title: 'Missing setting key' title: 'Missing setting key'
})); }))
} }
if (!_.isString(key)) { if (!_.isString(key)) {
return Bluebird.reject(errors.createError({ return Bluebird.reject(errors.createError({
title: `Invalid setting key: ${key}` title: `Invalid setting key: ${key}`
})); }))
} }
return exports.assign({ return exports.assign({
[key]: value [key]: value
}); })
}; }
/** /**
* @summary Get a setting value * @summary Get a setting value
@ -177,8 +177,8 @@ exports.set = (key, value) => {
* const value = settings.get('unmountOnSuccess'); * const value = settings.get('unmountOnSuccess');
*/ */
exports.get = (key) => { exports.get = (key) => {
return _.get(exports.getAll(), [ key ]); return _.get(exports.getAll(), [ key ])
}; }
/** /**
* @summary Get all setting values * @summary Get all setting values
@ -192,5 +192,5 @@ exports.get = (key) => {
* console.log(allSettings.unmountOnSuccess); * console.log(allSettings.unmountOnSuccess);
*/ */
exports.getAll = () => { exports.getAll = () => {
return store.getState().get('settings').toJS(); return store.getState().get('settings').toJS()
}; }

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const resinCorvus = require('resin-corvus/browser'); const resinCorvus = require('resin-corvus/browser')
const packageJSON = require('../../../package.json'); const packageJSON = require('../../../package.json')
const settings = require('../models/settings'); const settings = require('../models/settings')
resinCorvus.install({ resinCorvus.install({
services: { services: {
@ -29,10 +29,10 @@ resinCorvus.install({
options: { options: {
release: packageJSON.version, release: packageJSON.version,
shouldReport: () => { shouldReport: () => {
return settings.get('errorReporting'); return settings.get('errorReporting')
} }
} }
}); })
/** /**
* @summary Log a debug message * @summary Log a debug message
@ -47,7 +47,7 @@ resinCorvus.install({
* @example * @example
* analytics.log('Hello World'); * analytics.log('Hello World');
*/ */
exports.logDebug = resinCorvus.logDebug; exports.logDebug = resinCorvus.logDebug
/** /**
* @summary Log an event * @summary Log an event
@ -65,7 +65,7 @@ exports.logDebug = resinCorvus.logDebug;
* image: '/dev/disk2' * image: '/dev/disk2'
* }); * });
*/ */
exports.logEvent = resinCorvus.logEvent; exports.logEvent = resinCorvus.logEvent
/** /**
* @summary Log an exception * @summary Log an exception
@ -80,4 +80,4 @@ exports.logEvent = resinCorvus.logEvent;
* @example * @example
* analytics.logException(new Error('Something happened')); * analytics.logException(new Error('Something happened'));
*/ */
exports.logException = resinCorvus.logException; exports.logException = resinCorvus.logException

View File

@ -14,17 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Rx = require('rx'); const Rx = require('rx')
const _ = require('lodash'); const _ = require('lodash')
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter
const drivelist = require('drivelist'); const drivelist = require('drivelist')
const settings = require('../models/settings'); const settings = require('../models/settings')
const DRIVE_SCANNER_INTERVAL_MS = 2000; const DRIVE_SCANNER_INTERVAL_MS = 2000
const DRIVE_SCANNER_FIRST_SCAN_DELAY_MS = 0; const DRIVE_SCANNER_FIRST_SCAN_DELAY_MS = 0
const emitter = new EventEmitter(); const emitter = new EventEmitter()
/* eslint-disable lodash/prefer-lodash-method */ /* eslint-disable lodash/prefer-lodash-method */
@ -36,19 +36,19 @@ const availableDrives = Rx.Observable.timer(
/* eslint-enable lodash/prefer-lodash-method */ /* eslint-enable lodash/prefer-lodash-method */
.flatMap(() => { .flatMap(() => {
return Rx.Observable.fromNodeCallback(drivelist.list)(); return Rx.Observable.fromNodeCallback(drivelist.list)()
}) })
.map((drives) => { .map((drives) => {
if (settings.get('unsafeMode')) { if (settings.get('unsafeMode')) {
return drives; return drives
} }
return _.reject(drives, { return _.reject(drives, {
system: true system: true
});
}) })
.pausable(new Rx.Subject()); })
.pausable(new Rx.Subject())
/* /*
* This service emits the following events: * This service emits the following events:
@ -69,10 +69,10 @@ const availableDrives = Rx.Observable.timer(
* ``` * ```
*/ */
availableDrives.subscribe((drives) => { availableDrives.subscribe((drives) => {
emitter.emit('drives', drives); emitter.emit('drives', drives)
}, (error) => { }, (error) => {
emitter.emit('error', error); emitter.emit('error', error)
}); })
/** /**
* @summary Start scanning drives * @summary Start scanning drives
@ -83,8 +83,8 @@ availableDrives.subscribe((drives) => {
* driveScanner.start(); * driveScanner.start();
*/ */
emitter.start = () => { emitter.start = () => {
availableDrives.resume(); availableDrives.resume()
}; }
/** /**
* @summary Stop scanning drives * @summary Stop scanning drives
@ -95,7 +95,7 @@ emitter.start = () => {
* driveScanner.stop(); * driveScanner.stop();
*/ */
emitter.stop = () => { emitter.stop = () => {
availableDrives.pause(); availableDrives.pause()
}; }
module.exports = emitter; module.exports = emitter

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const analytics = require('../modules/analytics'); const analytics = require('../modules/analytics')
const osDialog = require('../os/dialog'); const osDialog = require('../os/dialog')
/** /**
* @summary Report an exception * @summary Report an exception
@ -32,9 +32,9 @@ const osDialog = require('../os/dialog');
*/ */
exports.report = (exception) => { exports.report = (exception) => {
if (_.isUndefined(exception)) { if (_.isUndefined(exception)) {
return; return
} }
osDialog.showError(exception); osDialog.showError(exception)
analytics.logException(exception); analytics.logException(exception)
}; }

View File

@ -14,23 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Modules.ImageWriter * @module Etcher.Modules.ImageWriter
*/ */
const angular = require('angular'); const angular = require('angular')
const _ = require('lodash'); const _ = require('lodash')
const childWriter = require('../../child-writer'); const childWriter = require('../../child-writer')
const settings = require('../models/settings'); const settings = require('../models/settings')
const flashState = require('../../shared/models/flash-state'); const flashState = require('../../shared/models/flash-state')
const errors = require('../../shared/errors'); const errors = require('../../shared/errors')
const windowProgress = require('../os/window-progress'); const windowProgress = require('../os/window-progress')
const analytics = require('../modules/analytics'); const analytics = require('../modules/analytics')
const MODULE_NAME = 'Etcher.Modules.ImageWriter'; const MODULE_NAME = 'Etcher.Modules.ImageWriter'
const imageWriter = angular.module(MODULE_NAME, []); const imageWriter = angular.module(MODULE_NAME, [])
imageWriter.service('ImageWriterService', function ($q, $rootScope) { imageWriter.service('ImageWriterService', function ($q, $rootScope) {
/** /**
@ -60,12 +60,12 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
const child = childWriter.write(image, drive, { const child = childWriter.write(image, drive, {
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'), validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
unmountOnSuccess: settings.get('unmountOnSuccess') unmountOnSuccess: settings.get('unmountOnSuccess')
}); })
child.on('error', reject); child.on('error', reject)
child.on('done', resolve); child.on('done', resolve)
child.on('progress', onProgress); child.on('progress', onProgress)
}); })
}; }
/** /**
* @summary Flash an image to a drive * @summary Flash an image to a drive
@ -88,10 +88,10 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
*/ */
this.flash = (image, drive) => { this.flash = (image, drive) => {
if (flashState.isFlashing()) { if (flashState.isFlashing()) {
return $q.reject(new Error('There is already a flash in progress')); return $q.reject(new Error('There is already a flash in progress'))
} }
flashState.setFlashingFlag(); flashState.setFlashingFlag()
const analyticsData = { const analyticsData = {
image, image,
@ -99,9 +99,9 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
uuid: flashState.getFlashUuid(), uuid: flashState.getFlashUuid(),
unmountOnSuccess: settings.get('unmountOnSuccess'), unmountOnSuccess: settings.get('unmountOnSuccess'),
validateWriteOnSuccess: settings.get('validateWriteOnSuccess') validateWriteOnSuccess: settings.get('validateWriteOnSuccess')
}; }
analytics.logEvent('Flash', analyticsData); analytics.logEvent('Flash', analyticsData)
return this.performWrite(image, drive, (state) => { return this.performWrite(image, drive, (state) => {
// Bring this value to the world of angular. // Bring this value to the world of angular.
@ -109,38 +109,38 @@ imageWriter.service('ImageWriterService', function ($q, $rootScope) {
// `.getFlashState()` will not return // `.getFlashState()` will not return
// the latest updated progress state. // the latest updated progress state.
$rootScope.$apply(() => { $rootScope.$apply(() => {
flashState.setProgressState(state); flashState.setProgressState(state)
}); })
}).then(flashState.unsetFlashingFlag).then(() => { }).then(flashState.unsetFlashingFlag).then(() => {
if (flashState.wasLastFlashCancelled()) { if (flashState.wasLastFlashCancelled()) {
analytics.logEvent('Elevation cancelled', analyticsData); analytics.logEvent('Elevation cancelled', analyticsData)
} else { } else {
analytics.logEvent('Done', analyticsData); analytics.logEvent('Done', analyticsData)
} }
}).catch((error) => { }).catch((error) => {
flashState.unsetFlashingFlag({ flashState.unsetFlashingFlag({
errorCode: error.code errorCode: error.code
}); })
if (error.code === 'EVALIDATION') { if (error.code === 'EVALIDATION') {
analytics.logEvent('Validation error', analyticsData); analytics.logEvent('Validation error', analyticsData)
} else if (error.code === 'EUNPLUGGED') { } else if (error.code === 'EUNPLUGGED') {
analytics.logEvent('Drive unplugged', analyticsData); analytics.logEvent('Drive unplugged', analyticsData)
} else if (error.code === 'EIO') { } else if (error.code === 'EIO') {
analytics.logEvent('Input/output error', analyticsData); analytics.logEvent('Input/output error', analyticsData)
} else if (error.code === 'ENOSPC') { } else if (error.code === 'ENOSPC') {
analytics.logEvent('Out of space', analyticsData); analytics.logEvent('Out of space', analyticsData)
} else { } else {
analytics.logEvent('Flash error', _.merge({ analytics.logEvent('Flash error', _.merge({
error: errors.toJSON(error) error: errors.toJSON(error)
}, analyticsData)); }, analyticsData))
} }
return $q.reject(error); return $q.reject(error)
}).finally(() => { }).finally(() => {
windowProgress.clear(); windowProgress.clear()
}); })
}; }
}); })
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,20 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const electron = require('electron'); const electron = require('electron')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const errors = require('../../shared/errors'); const errors = require('../../shared/errors')
const supportedFormats = require('../../shared/supported-formats'); const supportedFormats = require('../../shared/supported-formats')
/** /**
* @summary Current renderer BrowserWindow instance * @summary Current renderer BrowserWindow instance
* @type {Object} * @type {Object}
* @private * @private
*/ */
const currentWindow = electron.remote.getCurrentWindow(); const currentWindow = electron.remote.getCurrentWindow()
/** /**
* @summary Open an image selection dialog * @summary Open an image selection dialog
@ -71,10 +71,10 @@ exports.selectImage = () => {
// `_.first` is smart enough to not throw and return `undefined` // `_.first` is smart enough to not throw and return `undefined`
// if we pass it an `undefined` value (e.g: when the selection // if we pass it an `undefined` value (e.g: when the selection
// dialog was cancelled). // dialog was cancelled).
return resolve(_.first(files)); return resolve(_.first(files))
}); })
}); })
}; }
/** /**
* @summary Open a warning dialog * @summary Open a warning dialog
@ -105,15 +105,15 @@ exports.showWarning = (options) => {
_.defaults(options, { _.defaults(options, {
confirmationLabel: 'OK', confirmationLabel: 'OK',
rejectionLabel: 'Cancel' rejectionLabel: 'Cancel'
}); })
const BUTTONS = [ const BUTTONS = [
options.confirmationLabel, options.confirmationLabel,
options.rejectionLabel options.rejectionLabel
]; ]
const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, options.confirmationLabel); const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, options.confirmationLabel)
const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, options.rejectionLabel); const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, options.rejectionLabel)
return new Bluebird((resolve) => { return new Bluebird((resolve) => {
electron.remote.dialog.showMessageBox(currentWindow, { electron.remote.dialog.showMessageBox(currentWindow, {
@ -125,10 +125,10 @@ exports.showWarning = (options) => {
message: options.title, message: options.title,
detail: options.description detail: options.description
}, (response) => { }, (response) => {
return resolve(response === BUTTON_CONFIRMATION_INDEX); return resolve(response === BUTTON_CONFIRMATION_INDEX)
}); })
}); })
}; }
/** /**
* @summary Show error dialog for an Error instance * @summary Show error dialog for an Error instance
@ -141,7 +141,7 @@ exports.showWarning = (options) => {
* osDialog.showError(new Error('Foo Bar')); * osDialog.showError(new Error('Foo Bar'));
*/ */
exports.showError = (error) => { exports.showError = (error) => {
const title = errors.getTitle(error); const title = errors.getTitle(error)
const message = errors.getDescription(error); const message = errors.getDescription(error)
electron.remote.dialog.showErrorBox(title, message); electron.remote.dialog.showErrorBox(title, message)
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @summary Dropzone directive * @summary Dropzone directive
@ -40,18 +40,18 @@ module.exports = ($timeout) => {
osDropzone: '&' osDropzone: '&'
}, },
link: (scope, $element) => { link: (scope, $element) => {
const domElement = _.first($element); const domElement = _.first($element)
// See https://github.com/electron/electron/blob/master/docs/api/file-object.md // See https://github.com/electron/electron/blob/master/docs/api/file-object.md
// We're not interested in these events // We're not interested in these events
domElement.ondragover = _.constant(false); domElement.ondragover = _.constant(false)
domElement.ondragleave = _.constant(false); domElement.ondragleave = _.constant(false)
domElement.ondragend = _.constant(false); domElement.ondragend = _.constant(false)
domElement.ondrop = (event) => { domElement.ondrop = (event) => {
event.preventDefault(); event.preventDefault()
const filename = _.first(event.dataTransfer.files).path; const filename = _.first(event.dataTransfer.files).path
// Safely bring this to the word of Angular // Safely bring this to the word of Angular
$timeout(() => { $timeout(() => {
@ -61,11 +61,11 @@ module.exports = ($timeout) => {
// parameter called `$file` // parameter called `$file`
$file: filename $file: filename
}); })
}); })
return false; return false
};
} }
}; }
}; }
}

View File

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.OS.Dropzone * @module Etcher.OS.Dropzone
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.OS.Dropzone'; const MODULE_NAME = 'Etcher.OS.Dropzone'
const OSDropzone = angular.module(MODULE_NAME, []); const OSDropzone = angular.module(MODULE_NAME, [])
OSDropzone.directive('osDropzone', require('./directives/dropzone')); OSDropzone.directive('osDropzone', require('./directives/dropzone'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const electron = require('electron'); const electron = require('electron')
/** /**
* @summary Send a notification * @summary Send a notification
@ -43,8 +43,8 @@ const electron = require('electron');
exports.send = (title, options) => { exports.send = (title, options) => {
// `app.dock` is only defined in OS X // `app.dock` is only defined in OS X
if (electron.remote.app.dock) { if (electron.remote.app.dock) {
electron.remote.app.dock.bounce(); electron.remote.app.dock.bounce()
} }
return new window.Notification(title, options); return new window.Notification(title, options)
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @summary OsOpenExternal directive * @summary OsOpenExternal directive
@ -38,11 +38,11 @@ module.exports = (OSOpenExternalService) => {
link: (scope, element, attributes) => { link: (scope, element, attributes) => {
// This directive might be added to elements // This directive might be added to elements
// other than buttons. // other than buttons.
element.css('cursor', 'pointer'); element.css('cursor', 'pointer')
element.on('click', () => { element.on('click', () => {
OSOpenExternalService.open(attributes.osOpenExternal); OSOpenExternalService.open(attributes.osOpenExternal)
}); })
} }
}; }
}; }

View File

@ -14,33 +14,33 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.OS.OpenExternal * @module Etcher.OS.OpenExternal
*/ */
const angular = require('angular'); const angular = require('angular')
const url = require('url'); const url = require('url')
const MODULE_NAME = 'Etcher.OS.OpenExternal'; const MODULE_NAME = 'Etcher.OS.OpenExternal'
const OSOpenExternal = angular.module(MODULE_NAME, []); const OSOpenExternal = angular.module(MODULE_NAME, [])
OSOpenExternal.service('OSOpenExternalService', require('./services/open-external')); OSOpenExternal.service('OSOpenExternalService', require('./services/open-external'))
OSOpenExternal.directive('osOpenExternal', require('./directives/open-external')); OSOpenExternal.directive('osOpenExternal', require('./directives/open-external'))
OSOpenExternal.run((OSOpenExternalService) => { OSOpenExternal.run((OSOpenExternalService) => {
document.addEventListener('click', (event) => { document.addEventListener('click', (event) => {
const target = event.target; const target = event.target
if (target.tagName === 'A' && angular.isDefined(target.href)) { if (target.tagName === 'A' && angular.isDefined(target.href)) {
// Electron interprets relative URLs as being relative to the // Electron interprets relative URLs as being relative to the
// current loaded URL (with `webContents.loadURL`) and expands // current loaded URL (with `webContents.loadURL`) and expands
// them to the corresponding absolute URL. If it's a `file://` // them to the corresponding absolute URL. If it's a `file://`
// URL, we don't want it opened in an external browser. // URL, we don't want it opened in an external browser.
if (url.parse(target.href).protocol !== 'file:') { if (url.parse(target.href).protocol !== 'file:') {
OSOpenExternalService.open(target.href); OSOpenExternalService.open(target.href)
} }
} }
}); })
}); })
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const electron = require('electron'); const electron = require('electron')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
module.exports = function () { module.exports = function () {
/** /**
@ -33,10 +33,10 @@ module.exports = function () {
this.open = (url) => { this.open = (url) => {
analytics.logEvent('Open external link', { analytics.logEvent('Open external link', {
url url
}); })
if (url) { if (url) {
electron.shell.openExternal(url); electron.shell.openExternal(url)
} }
}; }
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const electron = require('electron'); const electron = require('electron')
const utils = require('../../shared/utils'); const utils = require('../../shared/utils')
/** /**
* @summary A reference to the current renderer Electron window * @summary A reference to the current renderer Electron window
@ -27,7 +27,7 @@ const utils = require('../../shared/utils');
* @description * @description
* We expose this property to `this` for testability purposes. * We expose this property to `this` for testability purposes.
*/ */
exports.currentWindow = electron.remote.getCurrentWindow(); exports.currentWindow = electron.remote.getCurrentWindow()
/** /**
* @summary Set operating system window progress * @summary Set operating system window progress
@ -43,8 +43,8 @@ exports.currentWindow = electron.remote.getCurrentWindow();
* windowProgress.set(85); * windowProgress.set(85);
*/ */
exports.set = (percentage) => { exports.set = (percentage) => {
exports.currentWindow.setProgressBar(utils.percentageToFloat(percentage)); exports.currentWindow.setProgressBar(utils.percentageToFloat(percentage))
}; }
/** /**
* @summary Clear the window progress bar * @summary Clear the window progress bar
@ -56,7 +56,7 @@ exports.set = (percentage) => {
*/ */
exports.clear = () => { exports.clear = () => {
// Passing 0 or null/undefined doesn't work. // Passing 0 or null/undefined doesn't work.
const ELECTRON_PROGRESS_BAR_RESET_VALUE = -1; const ELECTRON_PROGRESS_BAR_RESET_VALUE = -1
exports.currentWindow.setProgressBar(ELECTRON_PROGRESS_BAR_RESET_VALUE); exports.currentWindow.setProgressBar(ELECTRON_PROGRESS_BAR_RESET_VALUE)
}; }

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const settings = require('../../../models/settings'); const settings = require('../../../models/settings')
const flashState = require('../../../../shared/models/flash-state'); const flashState = require('../../../../shared/models/flash-state')
const selectionState = require('../../../../shared/models/selection-state'); const selectionState = require('../../../../shared/models/selection-state')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
module.exports = function ($state) { module.exports = function ($state) {
/** /**
@ -27,14 +27,14 @@ module.exports = function ($state) {
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.settings = settings; this.settings = settings
/** /**
* @summary Source checksum * @summary Source checksum
* @type {String} * @type {String}
* @public * @public
*/ */
this.checksum = flashState.getLastFlashSourceChecksum(); this.checksum = flashState.getLastFlashSourceChecksum()
/** /**
* @summary Restart the flashing process * @summary Restart the flashing process
@ -48,8 +48,8 @@ module.exports = function ($state) {
* FinishController.restart({ preserveImage: true }); * FinishController.restart({ preserveImage: true });
*/ */
this.restart = (options) => { this.restart = (options) => {
selectionState.clear(options); selectionState.clear(options)
analytics.logEvent('Restart', options); analytics.logEvent('Restart', options)
$state.go('main'); $state.go('main')
}; }
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* The finish page represents the application state where * The finish page represents the application state where
@ -26,13 +26,13 @@
* @module Etcher.Pages.Finish * @module Etcher.Pages.Finish
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Pages.Finish'; const MODULE_NAME = 'Etcher.Pages.Finish'
const FinishPage = angular.module(MODULE_NAME, [ const FinishPage = angular.module(MODULE_NAME, [
require('angular-ui-router') require('angular-ui-router')
]); ])
FinishPage.controller('FinishController', require('./controllers/finish')); FinishPage.controller('FinishController', require('./controllers/finish'))
FinishPage.config(($stateProvider) => { FinishPage.config(($stateProvider) => {
$stateProvider $stateProvider
@ -40,7 +40,7 @@ FinishPage.config(($stateProvider) => {
url: '/success', url: '/success',
controller: 'FinishController as finish', controller: 'FinishController as finish',
templateUrl: './pages/finish/templates/success.tpl.html' templateUrl: './pages/finish/templates/success.tpl.html'
}); })
}); })
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const settings = require('../../../models/settings'); const settings = require('../../../models/settings')
const selectionState = require('../../../../shared/models/selection-state'); const selectionState = require('../../../../shared/models/selection-state')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter'); const exceptionReporter = require('../../../modules/exception-reporter')
module.exports = function (DriveSelectorService) { module.exports = function (DriveSelectorService) {
/** /**
@ -33,17 +33,17 @@ module.exports = function (DriveSelectorService) {
this.openDriveSelector = () => { this.openDriveSelector = () => {
DriveSelectorService.open().then((drive) => { DriveSelectorService.open().then((drive) => {
if (!drive) { if (!drive) {
return; return
} }
selectionState.setDrive(drive.device); selectionState.setDrive(drive.device)
analytics.logEvent('Select drive', { analytics.logEvent('Select drive', {
device: drive.device, device: drive.device,
unsafeMode: settings.get('unsafeMode') unsafeMode: settings.get('unsafeMode')
}); })
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}; }
/** /**
* @summary Reselect a drive * @summary Reselect a drive
@ -54,7 +54,7 @@ module.exports = function (DriveSelectorService) {
* DriveSelectionController.reselectDrive(); * DriveSelectionController.reselectDrive();
*/ */
this.reselectDrive = () => { this.reselectDrive = () => {
this.openDriveSelector(); this.openDriveSelector()
analytics.logEvent('Reselect drive'); analytics.logEvent('Reselect drive')
}; }
}; }

View File

@ -14,16 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const messages = require('../../../../shared/messages'); const messages = require('../../../../shared/messages')
const settings = require('../../../models/settings'); const settings = require('../../../models/settings')
const flashState = require('../../../../shared/models/flash-state'); const flashState = require('../../../../shared/models/flash-state')
const driveScanner = require('../../../modules/drive-scanner'); const driveScanner = require('../../../modules/drive-scanner')
const utils = require('../../../../shared/utils'); const utils = require('../../../../shared/utils')
const notification = require('../../../os/notification'); const notification = require('../../../os/notification')
const exceptionReporter = require('../../../modules/exception-reporter'); const exceptionReporter = require('../../../modules/exception-reporter')
const path = require('path'); const path = require('path')
module.exports = function ( module.exports = function (
$state, $state,
@ -52,14 +52,14 @@ module.exports = function (
*/ */
this.flashImageToDrive = (image, drive) => { this.flashImageToDrive = (image, drive) => {
if (flashState.isFlashing()) { if (flashState.isFlashing()) {
return; return
} }
// Stop scanning drives when flashing // Stop scanning drives when flashing
// otherwise Windows throws EPERM // otherwise Windows throws EPERM
driveScanner.stop(); driveScanner.stop()
const iconPath = '../../assets/icon.png'; const iconPath = '../../assets/icon.png'
ImageWriterService.flash(image.path, drive).then(() => { ImageWriterService.flash(image.path, drive).then(() => {
if (!flashState.wasLastFlashCancelled()) { if (!flashState.wasLastFlashCancelled()) {
@ -69,8 +69,8 @@ module.exports = function (
drive drive
}), }),
icon: iconPath icon: iconPath
}); })
$state.go('success'); $state.go('success')
} }
}) })
.catch((error) => { .catch((error) => {
@ -80,27 +80,27 @@ module.exports = function (
drive drive
}), }),
icon: iconPath icon: iconPath
}); })
// TODO: All these error codes to messages translations // TODO: All these error codes to messages translations
// should go away if the writer emitted user friendly // should go away if the writer emitted user friendly
// messages on the first place. // messages on the first place.
if (error.code === 'EVALIDATION') { if (error.code === 'EVALIDATION') {
FlashErrorModalService.show(messages.error.validation()); FlashErrorModalService.show(messages.error.validation())
} else if (error.code === 'EUNPLUGGED') { } else if (error.code === 'EUNPLUGGED') {
FlashErrorModalService.show(messages.error.driveUnplugged()); FlashErrorModalService.show(messages.error.driveUnplugged())
} else if (error.code === 'EIO') { } else if (error.code === 'EIO') {
FlashErrorModalService.show(messages.error.inputOutput()); FlashErrorModalService.show(messages.error.inputOutput())
} else if (error.code === 'ENOSPC') { } else if (error.code === 'ENOSPC') {
FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive()); FlashErrorModalService.show(messages.error.notEnoughSpaceInDrive())
} else { } else {
FlashErrorModalService.show(messages.error.genericFlashError()); FlashErrorModalService.show(messages.error.genericFlashError())
exceptionReporter.report(error); exceptionReporter.report(error)
} }
}).finally(() => { }).finally(() => {
driveScanner.start(); driveScanner.start()
}); })
}; }
/** /**
* @summary Get progress button label * @summary Get progress button label
@ -113,23 +113,23 @@ module.exports = function (
* const label = FlashController.getProgressButtonLabel(); * const label = FlashController.getProgressButtonLabel();
*/ */
this.getProgressButtonLabel = () => { this.getProgressButtonLabel = () => {
const currentFlashState = flashState.getFlashState(); const currentFlashState = flashState.getFlashState()
const isChecking = currentFlashState.type === 'check'; const isChecking = currentFlashState.type === 'check'
if (!flashState.isFlashing()) { if (!flashState.isFlashing()) {
return 'Flash!'; return 'Flash!'
} else if (currentFlashState.percentage === utils.PERCENTAGE_MINIMUM && !currentFlashState.speed) { } else if (currentFlashState.percentage === utils.PERCENTAGE_MINIMUM && !currentFlashState.speed) {
return 'Starting...'; return 'Starting...'
} else if (currentFlashState.percentage === utils.PERCENTAGE_MAXIMUM) { } else if (currentFlashState.percentage === utils.PERCENTAGE_MAXIMUM) {
if (isChecking && settings.get('unmountOnSuccess')) { if (isChecking && settings.get('unmountOnSuccess')) {
return 'Unmounting...'; return 'Unmounting...'
} }
return 'Finishing...'; return 'Finishing...'
} else if (isChecking) { } else if (isChecking) {
return `${currentFlashState.percentage}% Validating...`; return `${currentFlashState.percentage}% Validating...`
} }
return `${currentFlashState.percentage}%`; return `${currentFlashState.percentage}%`
}; }
}; }

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const path = require('path'); const path = require('path')
const messages = require('../../../../shared/messages'); const messages = require('../../../../shared/messages')
const errors = require('../../../../shared/errors'); const errors = require('../../../../shared/errors')
const imageStream = require('../../../../image-stream'); const imageStream = require('../../../../image-stream')
const supportedFormats = require('../../../../shared/supported-formats'); const supportedFormats = require('../../../../shared/supported-formats')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
const selectionState = require('../../../../shared/models/selection-state'); const selectionState = require('../../../../shared/models/selection-state')
const osDialog = require('../../../os/dialog'); const osDialog = require('../../../os/dialog')
const exceptionReporter = require('../../../modules/exception-reporter'); const exceptionReporter = require('../../../modules/exception-reporter')
module.exports = function ( module.exports = function (
$timeout, $timeout,
@ -42,7 +42,7 @@ module.exports = function (
'img', 'img',
'iso', 'iso',
'zip' 'zip'
], supportedFormats.getAllExtensions()); ], supportedFormats.getAllExtensions())
/** /**
* @summary Extra supported extensions * @summary Extra supported extensions
@ -53,7 +53,7 @@ module.exports = function (
this.extraSupportedExtensions = _.difference( this.extraSupportedExtensions = _.difference(
supportedFormats.getAllExtensions(), supportedFormats.getAllExtensions(),
this.mainSupportedExtensions this.mainSupportedExtensions
).sort(); ).sort()
/** /**
* @summary Select image * @summary Select image
@ -73,22 +73,22 @@ module.exports = function (
description: messages.error.invalidImage({ description: messages.error.invalidImage({
image image
}) })
}); })
osDialog.showError(invalidImageError); osDialog.showError(invalidImageError)
analytics.logEvent('Invalid image', image); analytics.logEvent('Invalid image', image)
return; return
} }
Bluebird.try(() => { Bluebird.try(() => {
let message = null; let message = null
if (supportedFormats.looksLikeWindowsImage(image.path)) { if (supportedFormats.looksLikeWindowsImage(image.path)) {
analytics.logEvent('Possibly Windows image', image); analytics.logEvent('Possibly Windows image', image)
message = messages.warning.looksLikeWindowsImage(); message = messages.warning.looksLikeWindowsImage()
} else if (!image.hasMBR) { } else if (!image.hasMBR) {
analytics.logEvent('Missing partition table', image); analytics.logEvent('Missing partition table', image)
message = messages.warning.missingPartitionTable(); message = messages.warning.missingPartitionTable()
} }
if (message) { if (message) {
@ -98,25 +98,25 @@ module.exports = function (
confirmationLabel: 'Change', confirmationLabel: 'Change',
rejectionLabel: 'Continue', rejectionLabel: 'Continue',
description: message description: message
}); })
} }
return false; return false
}).then((shouldChange) => { }).then((shouldChange) => {
if (shouldChange) { if (shouldChange) {
return this.reselectImage(); return this.reselectImage()
} }
selectionState.setImage(image); selectionState.setImage(image)
// An easy way so we can quickly identify if we're making use of // An easy way so we can quickly identify if we're making use of
// certain features without printing pages of text to DevTools. // certain features without printing pages of text to DevTools.
image.logo = Boolean(image.logo); image.logo = Boolean(image.logo)
image.bmap = Boolean(image.bmap); image.bmap = Boolean(image.bmap)
return analytics.logEvent('Select image', image); return analytics.logEvent('Select image', image)
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}; }
/** /**
* @summary Select an image by path * @summary Select an image by path
@ -132,8 +132,8 @@ module.exports = function (
imageStream.getImageMetadata(imagePath) imageStream.getImageMetadata(imagePath)
.then((imageMetadata) => { .then((imageMetadata) => {
$timeout(() => { $timeout(() => {
this.selectImage(imageMetadata); this.selectImage(imageMetadata)
}); })
}) })
.catch((error) => { .catch((error) => {
const imageError = errors.createUserError({ const imageError = errors.createUserError({
@ -142,12 +142,12 @@ module.exports = function (
imageBasename: path.basename(imagePath), imageBasename: path.basename(imagePath),
errorMessage: error.message errorMessage: error.message
}) })
}); })
osDialog.showError(imageError); osDialog.showError(imageError)
analytics.logException(error); analytics.logException(error)
}); })
}; }
/** /**
* @summary Open image selector * @summary Open image selector
@ -158,19 +158,19 @@ module.exports = function (
* ImageSelectionController.openImageSelector(); * ImageSelectionController.openImageSelector();
*/ */
this.openImageSelector = () => { this.openImageSelector = () => {
analytics.logEvent('Open image selector'); analytics.logEvent('Open image selector')
osDialog.selectImage().then((imagePath) => { osDialog.selectImage().then((imagePath) => {
// Avoid analytics and selection state changes // Avoid analytics and selection state changes
// if no file was resolved from the dialog. // if no file was resolved from the dialog.
if (!imagePath) { if (!imagePath) {
analytics.logEvent('Image selector closed'); analytics.logEvent('Image selector closed')
return; return
} }
this.selectImageByPath(imagePath); this.selectImageByPath(imagePath)
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}; }
/** /**
* @summary Reselect image * @summary Reselect image
@ -183,10 +183,10 @@ module.exports = function (
this.reselectImage = () => { this.reselectImage = () => {
analytics.logEvent('Reselect image', { analytics.logEvent('Reselect image', {
previousImage: selectionState.getImage() previousImage: selectionState.getImage()
}); })
this.openImageSelector(); this.openImageSelector()
}; }
/** /**
* @summary Get the basename of the selected image * @summary Get the basename of the selected image
@ -200,9 +200,9 @@ module.exports = function (
*/ */
this.getImageBasename = () => { this.getImageBasename = () => {
if (!selectionState.hasImage()) { if (!selectionState.hasImage()) {
return ''; return ''
} }
return path.basename(selectionState.getImagePath()); return path.basename(selectionState.getImagePath())
}; }
}; }

View File

@ -14,25 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const settings = require('../../../models/settings'); const settings = require('../../../models/settings')
const flashState = require('../../../../shared/models/flash-state'); const flashState = require('../../../../shared/models/flash-state')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter'); const exceptionReporter = require('../../../modules/exception-reporter')
const availableDrives = require('../../../../shared/models/available-drives'); const availableDrives = require('../../../../shared/models/available-drives')
const selectionState = require('../../../../shared/models/selection-state'); const selectionState = require('../../../../shared/models/selection-state')
module.exports = function ( module.exports = function (
TooltipModalService, TooltipModalService,
OSOpenExternalService OSOpenExternalService
) { ) {
// Expose several modules to the template for convenience // Expose several modules to the template for convenience
this.selection = selectionState; this.selection = selectionState
this.drives = availableDrives; this.drives = availableDrives
this.state = flashState; this.state = flashState
this.settings = settings; this.settings = settings
this.external = OSOpenExternalService; this.external = OSOpenExternalService
/** /**
* @summary Determine if the drive step should be disabled * @summary Determine if the drive step should be disabled
@ -47,8 +47,8 @@ module.exports = function (
* } * }
*/ */
this.shouldDriveStepBeDisabled = () => { this.shouldDriveStepBeDisabled = () => {
return !selectionState.hasImage(); return !selectionState.hasImage()
}; }
/** /**
* @summary Determine if the flash step should be disabled * @summary Determine if the flash step should be disabled
@ -63,8 +63,8 @@ module.exports = function (
* } * }
*/ */
this.shouldFlashStepBeDisabled = () => { this.shouldFlashStepBeDisabled = () => {
return !selectionState.hasDrive() || this.shouldDriveStepBeDisabled(); return !selectionState.hasDrive() || this.shouldDriveStepBeDisabled()
}; }
/** /**
* @summary Display a tooltip with the selected image details * @summary Display a tooltip with the selected image details
@ -79,11 +79,11 @@ module.exports = function (
this.showSelectedImageDetails = () => { this.showSelectedImageDetails = () => {
analytics.logEvent('Show selected image tooltip', { analytics.logEvent('Show selected image tooltip', {
imagePath: selectionState.getImagePath() imagePath: selectionState.getImagePath()
}); })
return TooltipModalService.show({ return TooltipModalService.show({
title: 'Image File Name', title: 'Image File Name',
message: selectionState.getImagePath() message: selectionState.getImagePath()
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}; }
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* This page represents the application main page. * This page represents the application main page.
@ -22,10 +22,10 @@
* @module Etcher.Pages.Main * @module Etcher.Pages.Main
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Pages.Main'; const MODULE_NAME = 'Etcher.Pages.Main'
require('angular-moment'); require('angular-moment')
const MainPage = angular.module(MODULE_NAME, [ const MainPage = angular.module(MODULE_NAME, [
'angularMoment', 'angularMoment',
@ -45,12 +45,12 @@ const MainPage = angular.module(MODULE_NAME, [
require('../../modules/image-writer'), require('../../modules/image-writer'),
require('../../utils/byte-size/byte-size') require('../../utils/byte-size/byte-size')
]); ])
MainPage.controller('MainController', require('./controllers/main')); MainPage.controller('MainController', require('./controllers/main'))
MainPage.controller('ImageSelectionController', require('./controllers/image-selection')); MainPage.controller('ImageSelectionController', require('./controllers/image-selection'))
MainPage.controller('DriveSelectionController', require('./controllers/drive-selection')); MainPage.controller('DriveSelectionController', require('./controllers/drive-selection'))
MainPage.controller('FlashController', require('./controllers/flash')); MainPage.controller('FlashController', require('./controllers/flash'))
MainPage.config(($stateProvider) => { MainPage.config(($stateProvider) => {
$stateProvider $stateProvider
@ -58,7 +58,7 @@ MainPage.config(($stateProvider) => {
url: '/main', url: '/main',
controller: 'MainController as main', controller: 'MainController as main',
templateUrl: './pages/main/templates/main.tpl.html' templateUrl: './pages/main/templates/main.tpl.html'
}); })
}); })
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const os = require('os'); const os = require('os')
const _ = require('lodash'); const _ = require('lodash')
const settings = require('../../../models/settings'); const settings = require('../../../models/settings')
const analytics = require('../../../modules/analytics'); const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter'); const exceptionReporter = require('../../../modules/exception-reporter')
module.exports = function (WarningModalService) { module.exports = function (WarningModalService) {
/** /**
@ -29,7 +29,7 @@ module.exports = function (WarningModalService) {
* @constant * @constant
* @public * @public
*/ */
this.platform = os.platform(); this.platform = os.platform()
/** /**
* @summary Refresh current settings * @summary Refresh current settings
@ -40,23 +40,23 @@ module.exports = function (WarningModalService) {
* SettingsController.refreshSettings(); * SettingsController.refreshSettings();
*/ */
this.refreshSettings = () => { this.refreshSettings = () => {
this.currentData = settings.getAll(); this.currentData = settings.getAll()
}; }
/** /**
* @summary Current settings value * @summary Current settings value
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.currentData = {}; this.currentData = {}
this.refreshSettings(); this.refreshSettings()
/** /**
* @summary Settings model * @summary Settings model
* @type {Object} * @type {Object}
* @public * @public
*/ */
this.model = settings; this.model = settings
/** /**
* @summary Toggle setting * @summary Toggle setting
@ -81,27 +81,27 @@ module.exports = function (WarningModalService) {
* }); * });
*/ */
this.toggle = (setting, options) => { this.toggle = (setting, options) => {
const value = this.currentData[setting]; const value = this.currentData[setting]
const dangerous = !_.isUndefined(options); const dangerous = !_.isUndefined(options)
analytics.logEvent('Toggle setting', { analytics.logEvent('Toggle setting', {
setting, setting,
value, value,
dangerous dangerous
}); })
if (!value || !dangerous) { if (!value || !dangerous) {
return this.model.set(setting, value); return this.model.set(setting, value)
} }
// Keep the checkbox unchecked until the user confirms // Keep the checkbox unchecked until the user confirms
this.currentData[setting] = false; this.currentData[setting] = false
return WarningModalService.display(options).then((userAccepted) => { return WarningModalService.display(options).then((userAccepted) => {
if (userAccepted) { if (userAccepted) {
this.model.set(setting, true); this.model.set(setting, true)
this.refreshSettings(); this.refreshSettings()
} }
}).catch(exceptionReporter.report); }).catch(exceptionReporter.report)
}; }
}; }

View File

@ -14,20 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @module Etcher.Pages.Settings * @module Etcher.Pages.Settings
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Pages.Settings'; const MODULE_NAME = 'Etcher.Pages.Settings'
const SettingsPage = angular.module(MODULE_NAME, [ const SettingsPage = angular.module(MODULE_NAME, [
require('angular-ui-router'), require('angular-ui-router'),
require('../../components/warning-modal/warning-modal') require('../../components/warning-modal/warning-modal')
]); ])
SettingsPage.controller('SettingsController', require('./controllers/settings')); SettingsPage.controller('SettingsController', require('./controllers/settings'))
SettingsPage.config(($stateProvider) => { SettingsPage.config(($stateProvider) => {
$stateProvider $stateProvider
@ -35,7 +35,7 @@ SettingsPage.config(($stateProvider) => {
url: '/settings', url: '/settings',
controller: 'SettingsController as settings', controller: 'SettingsController as settings',
templateUrl: './pages/settings/templates/settings.tpl.html' templateUrl: './pages/settings/templates/settings.tpl.html'
}); })
}); })
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* The purpose of this module is to provide utilities * The purpose of this module is to provide utilities
@ -23,14 +23,14 @@
* @module Etcher.Utils.ByteSize * @module Etcher.Utils.ByteSize
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Utils.ByteSize'; const MODULE_NAME = 'Etcher.Utils.ByteSize'
const ByteSize = angular.module(MODULE_NAME, []); const ByteSize = angular.module(MODULE_NAME, [])
/* eslint-disable lodash/prefer-lodash-method */ /* eslint-disable lodash/prefer-lodash-method */
ByteSize.filter('closestUnit', require('./filter.js')); ByteSize.filter('closestUnit', require('./filter.js'))
/* eslint-enable lodash/prefer-lodash-method */ /* eslint-enable lodash/prefer-lodash-method */
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const units = require('../../../shared/units'); const units = require('../../../shared/units')
module.exports = () => { module.exports = () => {
/** /**
@ -30,5 +30,5 @@ module.exports = () => {
* @example * @example
* {{ 7801405440 | closestUnit }} * {{ 7801405440 | closestUnit }}
*/ */
return units.bytesToClosestUnit; return units.bytesToClosestUnit
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const errors = require('../../../../shared/errors'); const errors = require('../../../../shared/errors')
/** /**
* @summary ManifestBind directive * @summary ManifestBind directive
@ -38,15 +38,15 @@ module.exports = (ManifestBindService) => {
restrict: 'A', restrict: 'A',
scope: false, scope: false,
link: (scope, element, attributes) => { link: (scope, element, attributes) => {
const value = ManifestBindService.get(attributes.manifestBind); const value = ManifestBindService.get(attributes.manifestBind)
if (!value) { if (!value) {
throw errors.createError({ throw errors.createError({
title: `ManifestBind: Unknown property \`${attributes.manifestBind}\`` title: `ManifestBind: Unknown property \`${attributes.manifestBind}\``
}); })
} }
element.html(value); element.html(value)
} }
}; }
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* The purpose of this module is to provide an attribute * The purpose of this module is to provide an attribute
@ -24,10 +24,10 @@
* @module Etcher.Utils.ManifestBind * @module Etcher.Utils.ManifestBind
*/ */
const angular = require('angular'); const angular = require('angular')
const MODULE_NAME = 'Etcher.Utils.ManifestBind'; const MODULE_NAME = 'Etcher.Utils.ManifestBind'
const ManifestBind = angular.module(MODULE_NAME, []); const ManifestBind = angular.module(MODULE_NAME, [])
ManifestBind.service('ManifestBindService', require('./services/manifest-bind')); ManifestBind.service('ManifestBindService', require('./services/manifest-bind'))
ManifestBind.directive('manifestBind', require('./directives/manifest-bind')); ManifestBind.directive('manifestBind', require('./directives/manifest-bind'))
module.exports = MODULE_NAME; module.exports = MODULE_NAME

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const packageJSON = require('../../../../../package.json'); const packageJSON = require('../../../../../package.json')
module.exports = function () { module.exports = function () {
/** /**
@ -32,6 +32,6 @@ module.exports = function () {
* const version = ManifestBindService.get('version'); * const version = ManifestBindService.get('version');
*/ */
this.get = (attribute) => { this.get = (attribute) => {
return _.get(packageJSON, attribute); return _.get(packageJSON, attribute)
}; }
}; }

View File

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const _ = require('lodash'); const _ = require('lodash')
const StreamZip = require('node-stream-zip'); const StreamZip = require('node-stream-zip')
const yauzl = Bluebird.promisifyAll(require('yauzl')); const yauzl = Bluebird.promisifyAll(require('yauzl'))
const errors = require('../../shared/errors'); const errors = require('../../shared/errors')
/** /**
* @summary Get all archive entries * @summary Get all archive entries
@ -44,12 +44,12 @@ exports.getEntries = (archive) => {
const zip = new StreamZip({ const zip = new StreamZip({
file: archive, file: archive,
storeEntries: true storeEntries: true
}); })
zip.on('error', reject); zip.on('error', reject)
zip.on('ready', () => { zip.on('ready', () => {
const EMPTY_ENTRY_SIZE = 0; const EMPTY_ENTRY_SIZE = 0
return resolve(_.chain(zip.entries()) return resolve(_.chain(zip.entries())
.omitBy({ .omitBy({
@ -59,12 +59,12 @@ exports.getEntries = (archive) => {
return { return {
name: metadata.name, name: metadata.name,
size: metadata.size size: metadata.size
}; }
}) })
.value()); .value())
}); })
}); })
}; }
/** /**
* @summary Extract a file from an archive * @summary Extract a file from an archive
@ -91,27 +91,27 @@ exports.extractFile = (archive, entries, file) => {
})) { })) {
throw errors.createError({ throw errors.createError({
title: `Invalid entry: ${file}` title: `Invalid entry: ${file}`
}); })
} }
yauzl.openAsync(archive, { yauzl.openAsync(archive, {
lazyEntries: true lazyEntries: true
}).then((zipfile) => { }).then((zipfile) => {
zipfile.readEntry(); zipfile.readEntry()
zipfile.on('entry', (entry) => { zipfile.on('entry', (entry) => {
if (entry.fileName !== file) { if (entry.fileName !== file) {
return zipfile.readEntry(); return zipfile.readEntry()
} }
return zipfile.openReadStream(entry, (error, readStream) => { return zipfile.openReadStream(entry, (error, readStream) => {
if (error) { if (error) {
return reject(error); return reject(error)
} }
return resolve(readStream); return resolve(readStream)
}); })
}); })
}).catch(reject); }).catch(reject)
}); })
}; }

View File

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const _ = require('lodash'); const _ = require('lodash')
const PassThroughStream = require('stream').PassThrough; const PassThroughStream = require('stream').PassThrough
const supportedFileTypes = require('./supported'); const supportedFileTypes = require('./supported')
const utils = require('./utils'); const utils = require('./utils')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
const fileExtensions = require('../shared/file-extensions'); const fileExtensions = require('../shared/file-extensions')
/** /**
* @summary Archive metadata base path * @summary Archive metadata base path
@ -30,7 +30,7 @@ const fileExtensions = require('../shared/file-extensions');
* @private * @private
* @type {String} * @type {String}
*/ */
const ARCHIVE_METADATA_BASE_PATH = '.meta'; const ARCHIVE_METADATA_BASE_PATH = '.meta'
/** /**
* @summary Image extensions * @summary Image extensions
@ -40,11 +40,11 @@ const ARCHIVE_METADATA_BASE_PATH = '.meta';
*/ */
const IMAGE_EXTENSIONS = _.reduce(supportedFileTypes, (accumulator, file) => { const IMAGE_EXTENSIONS = _.reduce(supportedFileTypes, (accumulator, file) => {
if (file.type === 'image') { if (file.type === 'image') {
accumulator.push(file.extension); accumulator.push(file.extension)
} }
return accumulator; return accumulator
}, []); }, [])
/** /**
* @summary Extract entry by path * @summary Extract entry by path
@ -75,16 +75,16 @@ const extractEntryByPath = (archive, filePath, options) => {
.split('/') .split('/')
.tail() .tail()
.join('/') .join('/')
.value() === filePath; .value() === filePath
}); })
if (!fileEntry) { if (!fileEntry) {
return Bluebird.resolve(options.default); return Bluebird.resolve(options.default)
} }
return options.hooks.extractFile(archive, options.entries, fileEntry.name) return options.hooks.extractFile(archive, options.entries, fileEntry.name)
.then(utils.extractStream); .then(utils.extractStream)
}; }
/** /**
* @summary Extract archive metadata * @summary Extract archive metadata
@ -119,14 +119,14 @@ const extractArchiveMetadata = (archive, basePath, options) => {
default: '{}' default: '{}'
}).then((manifest) => { }).then((manifest) => {
try { try {
return JSON.parse(manifest); return JSON.parse(manifest)
} catch (parseError) { } catch (parseError) {
throw errors.createUserError({ throw errors.createUserError({
title: 'Invalid archive manifest.json', title: 'Invalid archive manifest.json',
description: 'The archive manifest.json file is not valid JSON' description: 'The archive manifest.json file is not valid JSON'
}); })
} }
}); })
}) })
}).then((results) => { }).then((results) => {
return { return {
@ -142,9 +142,9 @@ const extractArchiveMetadata = (archive, basePath, options) => {
logo: _.invoke(results.logo, [ 'toString' ]), logo: _.invoke(results.logo, [ 'toString' ]),
bmap: _.invoke(results.bmap, [ 'toString' ]), bmap: _.invoke(results.bmap, [ 'toString' ]),
instructions: _.invoke(results.instructions, [ 'toString' ]) instructions: _.invoke(results.instructions, [ 'toString' ])
}; }
}); })
}; }
/** /**
* @summary Extract image from archive * @summary Extract image from archive
@ -173,18 +173,18 @@ const extractArchiveMetadata = (archive, basePath, options) => {
exports.extractImage = (archive, hooks) => { exports.extractImage = (archive, hooks) => {
return hooks.getEntries(archive).then((entries) => { return hooks.getEntries(archive).then((entries) => {
const imageEntries = _.filter(entries, (entry) => { const imageEntries = _.filter(entries, (entry) => {
return _.includes(IMAGE_EXTENSIONS, fileExtensions.getLastFileExtension(entry.name)); return _.includes(IMAGE_EXTENSIONS, fileExtensions.getLastFileExtension(entry.name))
}); })
const VALID_NUMBER_OF_IMAGE_ENTRIES = 1; const VALID_NUMBER_OF_IMAGE_ENTRIES = 1
if (imageEntries.length !== VALID_NUMBER_OF_IMAGE_ENTRIES) { if (imageEntries.length !== VALID_NUMBER_OF_IMAGE_ENTRIES) {
throw errors.createUserError({ throw errors.createUserError({
title: 'Invalid archive image', title: 'Invalid archive image',
description: 'The archive image should contain one and only one top image file' description: 'The archive image should contain one and only one top image file'
}); })
} }
const imageEntry = _.first(imageEntries); const imageEntry = _.first(imageEntries)
return Bluebird.props({ return Bluebird.props({
imageStream: hooks.extractFile(archive, entries, imageEntry.name), imageStream: hooks.extractFile(archive, entries, imageEntry.name),
@ -193,9 +193,9 @@ exports.extractImage = (archive, hooks) => {
hooks hooks
}) })
}).then((results) => { }).then((results) => {
results.metadata.stream = results.imageStream; results.metadata.stream = results.imageStream
results.metadata.transform = new PassThroughStream(); results.metadata.transform = new PassThroughStream()
results.metadata.path = archive; results.metadata.path = archive
results.metadata.size = { results.metadata.size = {
original: imageEntry.size, original: imageEntry.size,
@ -203,12 +203,12 @@ exports.extractImage = (archive, hooks) => {
estimation: false, estimation: false,
value: imageEntry.size value: imageEntry.size
} }
}; }
results.metadata.extension = fileExtensions.getLastFileExtension(imageEntry.name); results.metadata.extension = fileExtensions.getLastFileExtension(imageEntry.name)
results.metadata.archiveExtension = fileExtensions.getLastFileExtension(archive); results.metadata.archiveExtension = fileExtensions.getLastFileExtension(archive)
return results.metadata; return results.metadata
}); })
}); })
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @summary The byte length of ISIZE * @summary The byte length of ISIZE
@ -23,7 +23,7 @@
* @description * @description
* See https://tools.ietf.org/html/rfc1952 * See https://tools.ietf.org/html/rfc1952
*/ */
const ISIZE_LENGTH = 4; const ISIZE_LENGTH = 4
/** /**
* @summary Get the estimated uncompressed size of a gzip file * @summary Get the estimated uncompressed size of a gzip file
@ -64,9 +64,9 @@ const ISIZE_LENGTH = 4;
* }); * });
*/ */
exports.getUncompressedSize = (options) => { exports.getUncompressedSize = (options) => {
const ISIZE_BUFFER_START = 0; const ISIZE_BUFFER_START = 0
const ISIZE_POSITION = options.size - ISIZE_LENGTH; const ISIZE_POSITION = options.size - ISIZE_LENGTH
return options.read(ISIZE_POSITION, ISIZE_LENGTH).then((buffer) => { return options.read(ISIZE_POSITION, ISIZE_LENGTH).then((buffer) => {
return buffer.readUInt32LE(ISIZE_BUFFER_START); return buffer.readUInt32LE(ISIZE_BUFFER_START)
}); })
}; }

View File

@ -14,24 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/* eslint-disable jsdoc/require-example */ /* eslint-disable jsdoc/require-example */
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const fs = Bluebird.promisifyAll(require('fs')); const fs = Bluebird.promisifyAll(require('fs'))
const PassThroughStream = require('stream').PassThrough; const PassThroughStream = require('stream').PassThrough
const lzma = Bluebird.promisifyAll(require('lzma-native')); const lzma = Bluebird.promisifyAll(require('lzma-native'))
const zlib = require('zlib'); const zlib = require('zlib')
const unbzip2Stream = require('unbzip2-stream'); const unbzip2Stream = require('unbzip2-stream')
const gzip = require('./gzip'); const gzip = require('./gzip')
const udif = Bluebird.promisifyAll(require('udif')); const udif = Bluebird.promisifyAll(require('udif'))
const archive = require('./archive'); const archive = require('./archive')
const utils = require('./utils'); const utils = require('./utils')
const zipArchiveHooks = require('./archive-hooks/zip'); const zipArchiveHooks = require('./archive-hooks/zip')
const fileExtensions = require('../shared/file-extensions'); const fileExtensions = require('../shared/file-extensions')
const path = require('path'); const path = require('path')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
/** /**
* @summary Image handlers * @summary Image handlers
@ -67,7 +67,7 @@ module.exports = {
} }
}, },
transform: unbzip2Stream() transform: unbzip2Stream()
}); })
}, },
/** /**
@ -85,14 +85,14 @@ module.exports = {
*/ */
'application/gzip': (imagePath, options) => { 'application/gzip': (imagePath, options) => {
return Bluebird.using(fs.openAsync(imagePath, 'r').disposer((fileDescriptor) => { return Bluebird.using(fs.openAsync(imagePath, 'r').disposer((fileDescriptor) => {
return fs.closeAsync(fileDescriptor); return fs.closeAsync(fileDescriptor)
}), (fileDescriptor) => { }), (fileDescriptor) => {
return gzip.getUncompressedSize({ return gzip.getUncompressedSize({
size: options.size, size: options.size,
read: (position, count) => { read: (position, count) => {
return utils.readBufferFromImageFileDescriptor(fileDescriptor, position, count); return utils.readBufferFromImageFileDescriptor(fileDescriptor, position, count)
} }
}); })
}).then((uncompressedSize) => { }).then((uncompressedSize) => {
return Bluebird.props({ return Bluebird.props({
path: imagePath, path: imagePath,
@ -107,8 +107,8 @@ module.exports = {
} }
}, },
transform: zlib.createGunzip() transform: zlib.createGunzip()
}); })
}); })
}, },
/** /**
@ -126,14 +126,14 @@ module.exports = {
*/ */
'application/x-xz': (imagePath, options) => { 'application/x-xz': (imagePath, options) => {
return Bluebird.using(fs.openAsync(imagePath, 'r').disposer((fileDescriptor) => { return Bluebird.using(fs.openAsync(imagePath, 'r').disposer((fileDescriptor) => {
return fs.closeAsync(fileDescriptor); return fs.closeAsync(fileDescriptor)
}), (fileDescriptor) => { }), (fileDescriptor) => {
return lzma.parseFileIndexAsync({ return lzma.parseFileIndexAsync({
fileSize: options.size, fileSize: options.size,
read: (count, position, callback) => { read: (count, position, callback) => {
utils.readBufferFromImageFileDescriptor(fileDescriptor, position, count).asCallback(callback); utils.readBufferFromImageFileDescriptor(fileDescriptor, position, count).asCallback(callback)
} }
}); })
}).then((metadata) => { }).then((metadata) => {
return { return {
path: imagePath, path: imagePath,
@ -148,8 +148,8 @@ module.exports = {
} }
}, },
transform: lzma.createDecompressor() transform: lzma.createDecompressor()
}; }
}); })
}, },
/** /**
@ -181,7 +181,7 @@ module.exports = {
} }
}, },
transform: new PassThroughStream() transform: new PassThroughStream()
}; }
}).catch((error) => { }).catch((error) => {
if (/invalid footer/i.test(error.message)) { if (/invalid footer/i.test(error.message)) {
throw errors.createUserError({ throw errors.createUserError({
@ -189,10 +189,10 @@ module.exports = {
description: `There was an error reading "${path.basename(imagePath)}". ` + description: `There was an error reading "${path.basename(imagePath)}". ` +
'The image does not appear to be a valid Apple Disk Image (dmg), or may have the wrong filename extension.\n\n' + 'The image does not appear to be a valid Apple Disk Image (dmg), or may have the wrong filename extension.\n\n' +
`Error: ${error.description || error.message}` `Error: ${error.description || error.message}`
}); })
} }
throw error; throw error
}); })
}, },
/** /**
@ -206,7 +206,7 @@ module.exports = {
* @returns {Promise} * @returns {Promise}
*/ */
'application/zip': (imagePath) => { 'application/zip': (imagePath) => {
return archive.extractImage(imagePath, zipArchiveHooks); return archive.extractImage(imagePath, zipArchiveHooks)
}, },
/** /**
@ -235,7 +235,7 @@ module.exports = {
} }
}, },
transform: new PassThroughStream() transform: new PassThroughStream()
}); })
} }
}; }

View File

@ -14,17 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const fs = Bluebird.promisifyAll(require('fs')); const fs = Bluebird.promisifyAll(require('fs'))
const stream = require('stream'); const stream = require('stream')
const mime = require('./mime'); const mime = require('./mime')
const handlers = require('./handlers'); const handlers = require('./handlers')
const supportedFileTypes = require('./supported'); const supportedFileTypes = require('./supported')
const errors = require('../shared/errors'); const errors = require('../shared/errors')
const parsePartitions = require('./parse-partitions'); const parsePartitions = require('./parse-partitions')
/** /**
* @summary Get an image stream from a file * @summary Get an image stream from a file
@ -73,19 +73,19 @@ exports.getFromFilePath = (file) => {
throw errors.createUserError({ throw errors.createUserError({
title: 'Invalid image', title: 'Invalid image',
description: 'The image must be a file' description: 'The image must be a file'
}); })
} }
return mime.getMimeTypeFromFileName(file).then((type) => { return mime.getMimeTypeFromFileName(file).then((type) => {
const mimeType = _.has(handlers, type) ? type : mime.DEFAULT_MIME_TYPE; const mimeType = _.has(handlers, type) ? type : mime.DEFAULT_MIME_TYPE
return _.invoke(handlers, mimeType, file, { return _.invoke(handlers, mimeType, file, {
size: fileStats.size size: fileStats.size
}); })
}); })
}).then((image) => { }).then((image) => {
return _.omitBy(image, _.isUndefined); return _.omitBy(image, _.isUndefined)
}); })
}; }
/** /**
* @summary Get image metadata * @summary Get image metadata
@ -120,10 +120,10 @@ exports.getImageMetadata = (file) => {
.then(parsePartitions) .then(parsePartitions)
.then((image) => { .then((image) => {
return _.omitBy(image, (property) => { return _.omitBy(image, (property) => {
return property instanceof stream.Stream || _.isNil(property); return property instanceof stream.Stream || _.isNil(property)
}); })
}); })
}; }
/** /**
* @summary Supported file types * @summary Supported file types
@ -137,4 +137,4 @@ exports.getImageMetadata = (file) => {
* console.log('Supported file type: ' + fileType.extension); * console.log('Supported file type: ' + fileType.extension);
* }); * });
*/ */
exports.supportedFileTypes = supportedFileTypes; exports.supportedFileTypes = supportedFileTypes

View File

@ -14,21 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const fs = Bluebird.promisifyAll(require('fs')); const fs = Bluebird.promisifyAll(require('fs'))
const fileType = require('file-type'); const fileType = require('file-type')
const mime = require('mime-types'); const mime = require('mime-types')
const utils = require('./utils'); const utils = require('./utils')
/** /**
* @summary The default MIME type * @summary The default MIME type
* @type {String} * @type {String}
* @constant * @constant
*/ */
exports.DEFAULT_MIME_TYPE = 'application/octet-stream'; exports.DEFAULT_MIME_TYPE = 'application/octet-stream'
/** /**
* @summary Get file's mime type, by reading the initial 262 bytes if necessary * @summary Get file's mime type, by reading the initial 262 bytes if necessary
@ -45,20 +45,20 @@ exports.DEFAULT_MIME_TYPE = 'application/octet-stream';
* }); * });
*/ */
exports.getMimeTypeFromFileName = (filename) => { exports.getMimeTypeFromFileName = (filename) => {
const mimeType = mime.lookup(filename); const mimeType = mime.lookup(filename)
if (mimeType) { if (mimeType) {
return Bluebird.resolve(mimeType); return Bluebird.resolve(mimeType)
} }
const FILE_TYPE_ID_START = 0; const FILE_TYPE_ID_START = 0
const FILE_TYPE_ID_BYTES = 262; const FILE_TYPE_ID_BYTES = 262
return Bluebird.using(fs.openAsync(filename, 'r').disposer((fileDescriptor) => { return Bluebird.using(fs.openAsync(filename, 'r').disposer((fileDescriptor) => {
return fs.closeAsync(fileDescriptor); return fs.closeAsync(fileDescriptor)
}), (fileDescriptor) => { }), (fileDescriptor) => {
return utils.readBufferFromImageFileDescriptor(fileDescriptor, FILE_TYPE_ID_START, FILE_TYPE_ID_BYTES).then((buffer) => { return utils.readBufferFromImageFileDescriptor(fileDescriptor, FILE_TYPE_ID_START, FILE_TYPE_ID_BYTES).then((buffer) => {
return _.get(fileType(buffer), [ 'mime' ], exports.DEFAULT_MIME_TYPE); return _.get(fileType(buffer), [ 'mime' ], exports.DEFAULT_MIME_TYPE)
}); })
}); })
}; }

View File

@ -14,40 +14,40 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const MBR = require('mbr'); const MBR = require('mbr')
const GPT = require('gpt'); const GPT = require('gpt')
/** /**
* @summary Maximum number of bytes to read from the stream * @summary Maximum number of bytes to read from the stream
* @type {Number} * @type {Number}
* @constant * @constant
*/ */
const MAX_STREAM_BYTES = 65536; const MAX_STREAM_BYTES = 65536
/** /**
* @summary Initial number of bytes read * @summary Initial number of bytes read
* @type {Number} * @type {Number}
* @constant * @constant
*/ */
const INITIAL_LENGTH = 0; const INITIAL_LENGTH = 0
/** /**
* @summary Initial block size * @summary Initial block size
* @type {Number} * @type {Number}
* @constant * @constant
*/ */
const INITIAL_BLOCK_SIZE = 512; const INITIAL_BLOCK_SIZE = 512
/** /**
* @summary Maximum block size to check for * @summary Maximum block size to check for
* @type {Number} * @type {Number}
* @constant * @constant
*/ */
const MAX_BLOCK_SIZE = 4096; const MAX_BLOCK_SIZE = 4096
/** /**
* @summary Attempt to parse the GPT from various block sizes * @summary Attempt to parse the GPT from various block sizes
@ -66,23 +66,23 @@ const MAX_BLOCK_SIZE = 4096;
* } * }
*/ */
const detectGPT = (buffer) => { const detectGPT = (buffer) => {
let blockSize = INITIAL_BLOCK_SIZE; let blockSize = INITIAL_BLOCK_SIZE
let gpt = null; let gpt = null
// Attempt to parse the GPT from several offsets, // Attempt to parse the GPT from several offsets,
// as the block size of the image may vary (512,1024,2048,4096); // as the block size of the image may vary (512,1024,2048,4096);
// For example, ISOs will usually have a block size of 4096, // For example, ISOs will usually have a block size of 4096,
// but raw images a block size of 512 bytes // but raw images a block size of 512 bytes
while (blockSize <= MAX_BLOCK_SIZE) { while (blockSize <= MAX_BLOCK_SIZE) {
gpt = _.attempt(GPT.parse, buffer.slice(blockSize)); gpt = _.attempt(GPT.parse, buffer.slice(blockSize))
if (!_.isError(gpt)) { if (!_.isError(gpt)) {
return gpt; return gpt
} }
blockSize += blockSize; blockSize += blockSize
} }
return null; return null
}; }
/** /**
* @summary Attempt to parse the MBR & GPT from a given buffer * @summary Attempt to parse the MBR & GPT from a given buffer
@ -100,13 +100,13 @@ const detectGPT = (buffer) => {
* } * }
*/ */
const parsePartitionTables = (image, buffer) => { const parsePartitionTables = (image, buffer) => {
const mbr = _.attempt(MBR.parse, buffer); const mbr = _.attempt(MBR.parse, buffer)
let gpt = null; let gpt = null
if (!_.isError(mbr)) { if (!_.isError(mbr)) {
image.hasMBR = true; image.hasMBR = true
gpt = detectGPT(buffer); gpt = detectGPT(buffer)
image.hasGPT = !_.isNil(gpt); image.hasGPT = !_.isNil(gpt)
} }
// As MBR and GPT partition entries have a different structure, // As MBR and GPT partition entries have a different structure,
@ -121,8 +121,8 @@ const parsePartitionTables = (image, buffer) => {
firstLBA: partition.firstLBA, firstLBA: partition.firstLBA,
lastLBA: partition.lastLBA, lastLBA: partition.lastLBA,
extended: false extended: false
}; }
}); })
} else if (image.hasMBR) { } else if (image.hasMBR) {
image.partitions = _.map(mbr.partitions, (partition) => { image.partitions = _.map(mbr.partitions, (partition) => {
return { return {
@ -132,10 +132,10 @@ const parsePartitionTables = (image, buffer) => {
firstLBA: partition.firstLBA, firstLBA: partition.firstLBA,
lastLBA: partition.lastLBA, lastLBA: partition.lastLBA,
extended: partition.extended extended: partition.extended
};
});
} }
}; })
}
}
/** /**
* @summary Attempt to read the MBR and GPT from an imagestream * @summary Attempt to read the MBR and GPT from an imagestream
@ -159,44 +159,44 @@ const parsePartitionTables = (image, buffer) => {
*/ */
module.exports = (image) => { module.exports = (image) => {
return new Bluebird((resolve, reject) => { return new Bluebird((resolve, reject) => {
const chunks = []; const chunks = []
let length = INITIAL_LENGTH; let length = INITIAL_LENGTH
let destroyed = false; let destroyed = false
image.hasMBR = false; image.hasMBR = false
image.hasGPT = false; image.hasGPT = false
let stream = image.stream.pipe(image.transform); let stream = image.stream.pipe(image.transform)
stream.on('error', reject); stream.on('error', reject)
// We need to use the "old" flowing mode here, // We need to use the "old" flowing mode here,
// as some dependencies don't implement the "readable" // as some dependencies don't implement the "readable"
// mode properly (i.e. bzip2) // mode properly (i.e. bzip2)
stream.on('data', (chunk) => { stream.on('data', (chunk) => {
chunks.push(chunk); chunks.push(chunk)
length += chunk.length; length += chunk.length
// Once we've read enough bytes, terminate the stream // Once we've read enough bytes, terminate the stream
if (length >= MAX_STREAM_BYTES && !destroyed) { if (length >= MAX_STREAM_BYTES && !destroyed) {
// Prefer close() over destroy(), as some streams // Prefer close() over destroy(), as some streams
// from dependencies exhibit quirky behavior when destroyed // from dependencies exhibit quirky behavior when destroyed
if (image.stream.close) { if (image.stream.close) {
image.stream.close(); image.stream.close()
} else { } else {
image.stream.destroy(); image.stream.destroy()
} }
// Remove references to stream to allow them being GCed // Remove references to stream to allow them being GCed
image.stream = null; image.stream = null
image.transform = null; image.transform = null
stream = null; stream = null
destroyed = true; destroyed = true
// Parse the MBR, GPT and partitions from the obtained buffer // Parse the MBR, GPT and partitions from the obtained buffer
parsePartitionTables(image, Buffer.concat(chunks)); parsePartitionTables(image, Buffer.concat(chunks))
resolve(image); resolve(image)
} }
}); })
}); })
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @summary Supported filename extensions * @summary Supported filename extensions
@ -77,4 +77,4 @@ module.exports = [
extension: 'rpi-sdimg', extension: 'rpi-sdimg',
type: 'image' type: 'image'
} }
]; ]

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const fs = Bluebird.promisifyAll(require('fs')); const fs = Bluebird.promisifyAll(require('fs'))
const errors = require('../shared/errors'); const errors = require('../shared/errors')
/** /**
* @summary Read a buffer from an image file descriptor * @summary Read a buffer from an image file descriptor
@ -39,19 +39,19 @@ const errors = require('../shared/errors');
* }); * });
*/ */
exports.readBufferFromImageFileDescriptor = (fileDescriptor, position, count) => { exports.readBufferFromImageFileDescriptor = (fileDescriptor, position, count) => {
const BUFFER_FILL_VALUE = 0; const BUFFER_FILL_VALUE = 0
const BUFFER_START_POSITION = 0; const BUFFER_START_POSITION = 0
const buffer = Buffer.alloc(count, BUFFER_FILL_VALUE); const buffer = Buffer.alloc(count, BUFFER_FILL_VALUE)
return fs.readAsync(fileDescriptor, buffer, BUFFER_START_POSITION, count, position).tap((bytesRead) => { return fs.readAsync(fileDescriptor, buffer, BUFFER_START_POSITION, count, position).tap((bytesRead) => {
if (bytesRead !== count) { if (bytesRead !== count) {
throw errors.createUserError({ throw errors.createUserError({
title: 'Looks like the image is truncated', title: 'Looks like the image is truncated',
description: `We tried to read ${count} bytes at ${position}, but got ${bytesRead} bytes instead` description: `We tried to read ${count} bytes at ${position}, but got ${bytesRead} bytes instead`
}); })
} }
}).return(buffer); }).return(buffer)
}; }
/** /**
* @summary Extract the data of a readable stream * @summary Extract the data of a readable stream
@ -75,15 +75,15 @@ exports.readBufferFromImageFileDescriptor = (fileDescriptor, position, count) =>
*/ */
exports.extractStream = (stream) => { exports.extractStream = (stream) => {
return new Bluebird((resolve, reject) => { return new Bluebird((resolve, reject) => {
const chunks = []; const chunks = []
stream.on('data', (chunk) => { stream.on('data', (chunk) => {
chunks.push(chunk); chunks.push(chunk)
}); })
stream.on('error', reject); stream.on('error', reject)
stream.on('end', () => { stream.on('end', () => {
resolve(Buffer.concat(chunks)); resolve(Buffer.concat(chunks))
}); })
}); })
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const pathIsInside = require('path-is-inside'); const pathIsInside = require('path-is-inside')
/** /**
* @summary The default unknown size for things such as images and drives * @summary The default unknown size for things such as images and drives
@ -25,7 +25,7 @@ const pathIsInside = require('path-is-inside');
* @private * @private
* @type {Number} * @type {Number}
*/ */
const UNKNOWN_SIZE = 0; const UNKNOWN_SIZE = 0
/** /**
* @summary Check if a drive is locked * @summary Check if a drive is locked
@ -49,8 +49,8 @@ const UNKNOWN_SIZE = 0;
* } * }
*/ */
exports.isDriveLocked = (drive) => { exports.isDriveLocked = (drive) => {
return Boolean(_.get(drive, [ 'protected' ], false)); return Boolean(_.get(drive, [ 'protected' ], false))
}; }
/** /**
* @summary Check if a drive is a system drive * @summary Check if a drive is a system drive
@ -71,8 +71,8 @@ exports.isDriveLocked = (drive) => {
* } * }
*/ */
exports.isSystemDrive = (drive) => { exports.isSystemDrive = (drive) => {
return Boolean(_.get(drive, [ 'system' ], false)); return Boolean(_.get(drive, [ 'system' ], false))
}; }
/** /**
* @summary Check if a drive is source drive * @summary Check if a drive is source drive
@ -108,17 +108,17 @@ exports.isSystemDrive = (drive) => {
* } * }
*/ */
exports.isSourceDrive = (drive, image) => { exports.isSourceDrive = (drive, image) => {
const mountpoints = _.get(drive, [ 'mountpoints' ], []); const mountpoints = _.get(drive, [ 'mountpoints' ], [])
const imagePath = _.get(image, [ 'path' ]); const imagePath = _.get(image, [ 'path' ])
if (!imagePath || _.isEmpty(mountpoints)) { if (!imagePath || _.isEmpty(mountpoints)) {
return false; return false
} }
return _.some(_.map(mountpoints, (mountpoint) => { return _.some(_.map(mountpoints, (mountpoint) => {
return pathIsInside(imagePath, mountpoint.path); return pathIsInside(imagePath, mountpoint.path)
})); }))
}; }
/** /**
* @summary Check if a drive is large enough for an image * @summary Check if a drive is large enough for an image
@ -142,7 +142,7 @@ exports.isSourceDrive = (drive, image) => {
* } * }
*/ */
exports.isDriveLargeEnough = (drive, image) => { exports.isDriveLargeEnough = (drive, image) => {
const driveSize = _.get(drive, [ 'size' ], UNKNOWN_SIZE); const driveSize = _.get(drive, [ 'size' ], UNKNOWN_SIZE)
if (_.get(image, [ 'size', 'final', 'estimation' ])) { if (_.get(image, [ 'size', 'final', 'estimation' ])) {
// If the drive size is smaller than the original image size, and // If the drive size is smaller than the original image size, and
@ -150,22 +150,22 @@ exports.isDriveLargeEnough = (drive, image) => {
// here, based on the assumption that the final size will never // here, based on the assumption that the final size will never
// be less than the original size. // be less than the original size.
if (driveSize < _.get(image, [ 'size', 'original' ], UNKNOWN_SIZE)) { if (driveSize < _.get(image, [ 'size', 'original' ], UNKNOWN_SIZE)) {
return false; return false
} }
// If the final image size is just an estimation then consider it // If the final image size is just an estimation then consider it
// large enough. In the worst case, the user gets an error saying // large enough. In the worst case, the user gets an error saying
// the drive has ran out of space, instead of prohibiting the flash // the drive has ran out of space, instead of prohibiting the flash
// at all, when the estimation may be wrong. // at all, when the estimation may be wrong.
return true; return true
} }
return driveSize >= _.get(image, [ return driveSize >= _.get(image, [
'size', 'size',
'final', 'final',
'value' 'value'
], UNKNOWN_SIZE); ], UNKNOWN_SIZE)
}; }
/** /**
* @summary Check if a drive is is valid, i.e. not locked and large enough for an image * @summary Check if a drive is is valid, i.e. not locked and large enough for an image
@ -195,8 +195,8 @@ exports.isDriveValid = (drive, image) => {
!this.isDriveLocked(drive), !this.isDriveLocked(drive),
this.isDriveLargeEnough(drive, image), this.isDriveLargeEnough(drive, image),
!this.isSourceDrive(drive, image) !this.isSourceDrive(drive, image)
]); ])
}; }
/** /**
* @summary Check if a drive meets the recommended drive size suggestion * @summary Check if a drive meets the recommended drive size suggestion
@ -228,8 +228,8 @@ exports.isDriveValid = (drive, image) => {
* } * }
*/ */
exports.isDriveSizeRecommended = (drive, image) => { exports.isDriveSizeRecommended = (drive, image) => {
return _.get(drive, [ 'size' ], UNKNOWN_SIZE) >= _.get(image, [ 'recommendedDriveSize' ], UNKNOWN_SIZE); return _.get(drive, [ 'size' ], UNKNOWN_SIZE) >= _.get(image, [ 'recommendedDriveSize' ], UNKNOWN_SIZE)
}; }
/** /**
* @summary Drive/image compatibility status messages. * @summary Drive/image compatibility status messages.
@ -286,7 +286,7 @@ exports.COMPATIBILITY_STATUS_MESSAGES = {
* The drive contains the image and therefore cannot be written to. * The drive contains the image and therefore cannot be written to.
*/ */
CONTAINS_IMAGE: 'Drive Contains Image' CONTAINS_IMAGE: 'Drive Contains Image'
}; }
/** /**
* @summary Drive/image compatibility status types. * @summary Drive/image compatibility status types.
@ -299,7 +299,7 @@ exports.COMPATIBILITY_STATUS_MESSAGES = {
exports.COMPATIBILITY_STATUS_TYPES = { exports.COMPATIBILITY_STATUS_TYPES = {
WARNING: 1, WARNING: 1,
ERROR: 2 ERROR: 2
}; }
/** /**
* @summary Get drive/image compatibility in an object * @summary Get drive/image compatibility in an object
@ -339,39 +339,39 @@ exports.COMPATIBILITY_STATUS_TYPES = {
* } * }
*/ */
exports.getDriveImageCompatibilityStatuses = (drive, image) => { exports.getDriveImageCompatibilityStatuses = (drive, image) => {
const statusList = []; const statusList = []
// Mind the order of the if-statements if you modify. // Mind the order of the if-statements if you modify.
if (exports.isSourceDrive(drive, image)) { if (exports.isSourceDrive(drive, image)) {
statusList.push({ statusList.push({
type: exports.COMPATIBILITY_STATUS_TYPES.ERROR, type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
message: exports.COMPATIBILITY_STATUS_MESSAGES.CONTAINS_IMAGE message: exports.COMPATIBILITY_STATUS_MESSAGES.CONTAINS_IMAGE
}); })
} else if (exports.isDriveLocked(drive)) { } else if (exports.isDriveLocked(drive)) {
statusList.push({ statusList.push({
type: exports.COMPATIBILITY_STATUS_TYPES.ERROR, type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
message: exports.COMPATIBILITY_STATUS_MESSAGES.LOCKED message: exports.COMPATIBILITY_STATUS_MESSAGES.LOCKED
}); })
} else if (!_.isNil(drive) && !exports.isDriveLargeEnough(drive, image)) { } else if (!_.isNil(drive) && !exports.isDriveLargeEnough(drive, image)) {
statusList.push({ statusList.push({
type: exports.COMPATIBILITY_STATUS_TYPES.ERROR, type: exports.COMPATIBILITY_STATUS_TYPES.ERROR,
message: exports.COMPATIBILITY_STATUS_MESSAGES.TOO_SMALL message: exports.COMPATIBILITY_STATUS_MESSAGES.TOO_SMALL
}); })
} else { } else {
if (exports.isSystemDrive(drive)) { if (exports.isSystemDrive(drive)) {
statusList.push({ statusList.push({
type: exports.COMPATIBILITY_STATUS_TYPES.WARNING, type: exports.COMPATIBILITY_STATUS_TYPES.WARNING,
message: exports.COMPATIBILITY_STATUS_MESSAGES.SYSTEM message: exports.COMPATIBILITY_STATUS_MESSAGES.SYSTEM
}); })
} }
if (!_.isNil(drive) && !exports.isDriveSizeRecommended(drive, image)) { if (!_.isNil(drive) && !exports.isDriveSizeRecommended(drive, image)) {
statusList.push({ statusList.push({
type: exports.COMPATIBILITY_STATUS_TYPES.WARNING, type: exports.COMPATIBILITY_STATUS_TYPES.WARNING,
message: exports.COMPATIBILITY_STATUS_MESSAGES.SIZE_NOT_RECOMMENDED message: exports.COMPATIBILITY_STATUS_MESSAGES.SIZE_NOT_RECOMMENDED
}); })
} }
} }
return statusList; return statusList
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @summary Create an error details object * @summary Create an error details object
@ -38,9 +38,9 @@ const _ = require('lodash');
*/ */
const createErrorDetails = (options) => { const createErrorDetails = (options) => {
return _.pick(_.mapValues(options, (value) => { return _.pick(_.mapValues(options, (value) => {
return _.isFunction(value) ? value : _.constant(value); return _.isFunction(value) ? value : _.constant(value)
}), [ 'title', 'description' ]); }), [ 'title', 'description' ])
}; }
/** /**
* @summary Human-friendly error messages * @summary Human-friendly error messages
@ -57,7 +57,7 @@ exports.HUMAN_FRIENDLY = {
*/ */
ENOENT: createErrorDetails({ ENOENT: createErrorDetails({
title: (error) => { title: (error) => {
return `No such file or directory: ${error.path}`; return `No such file or directory: ${error.path}`
}, },
description: 'The file you\'re trying to access doesn\'t exist' description: 'The file you\'re trying to access doesn\'t exist'
}), }),
@ -91,7 +91,7 @@ exports.HUMAN_FRIENDLY = {
/* eslint-enable new-cap */ /* eslint-enable new-cap */
}; }
/** /**
* @summary Get user friendly property from an error * @summary Get user friendly property from an error
@ -113,14 +113,14 @@ exports.HUMAN_FRIENDLY = {
* } * }
*/ */
const getUserFriendlyMessageProperty = (error, property) => { const getUserFriendlyMessageProperty = (error, property) => {
const code = _.get(error, [ 'code' ]); const code = _.get(error, [ 'code' ])
if (_.isNil(code) || !_.isString(code)) { if (_.isNil(code) || !_.isString(code)) {
return null; return null
} }
return _.invoke(exports.HUMAN_FRIENDLY, [ code, property ], error); return _.invoke(exports.HUMAN_FRIENDLY, [ code, property ], error)
}; }
/** /**
* @summary Check if a string is blank * @summary Check if a string is blank
@ -135,7 +135,7 @@ const getUserFriendlyMessageProperty = (error, property) => {
* console.log('The string is blank'); * console.log('The string is blank');
* } * }
*/ */
const isBlank = _.flow([ _.trim, _.isEmpty ]); const isBlank = _.flow([ _.trim, _.isEmpty ])
/** /**
* @summary Get the title of an error * @summary Get the title of an error
@ -156,26 +156,26 @@ const isBlank = _.flow([ _.trim, _.isEmpty ]);
*/ */
exports.getTitle = (error) => { exports.getTitle = (error) => {
if (!_.isError(error) && !_.isPlainObject(error) && !_.isNil(error)) { if (!_.isError(error) && !_.isPlainObject(error) && !_.isNil(error)) {
return _.toString(error); return _.toString(error)
} }
const codeTitle = getUserFriendlyMessageProperty(error, 'title'); const codeTitle = getUserFriendlyMessageProperty(error, 'title')
if (!_.isNil(codeTitle)) { if (!_.isNil(codeTitle)) {
return codeTitle; return codeTitle
} }
const message = _.get(error, [ 'message' ]); const message = _.get(error, [ 'message' ])
if (!isBlank(message)) { if (!isBlank(message)) {
return message; return message
} }
const code = _.get(error, [ 'code' ]); const code = _.get(error, [ 'code' ])
if (!_.isNil(code) && !isBlank(code)) { if (!_.isNil(code) && !isBlank(code)) {
return `Error code: ${code}`; return `Error code: ${code}`
} }
return 'An error ocurred'; return 'An error ocurred'
}; }
/** /**
* @summary Get the description of an error * @summary Get the description of an error
@ -195,36 +195,36 @@ exports.getTitle = (error) => {
exports.getDescription = (error, options = {}) => { exports.getDescription = (error, options = {}) => {
_.defaults(options, { _.defaults(options, {
userFriendlyDescriptionsOnly: false userFriendlyDescriptionsOnly: false
}); })
if (!_.isError(error) && !_.isPlainObject(error)) { if (!_.isError(error) && !_.isPlainObject(error)) {
return ''; return ''
} }
if (!isBlank(error.description)) { if (!isBlank(error.description)) {
return error.description; return error.description
} }
const codeDescription = getUserFriendlyMessageProperty(error, 'description'); const codeDescription = getUserFriendlyMessageProperty(error, 'description')
if (!_.isNil(codeDescription)) { if (!_.isNil(codeDescription)) {
return codeDescription; return codeDescription
} }
if (options.userFriendlyDescriptionsOnly) { if (options.userFriendlyDescriptionsOnly) {
return ''; return ''
} }
if (error.stack) { if (error.stack) {
return error.stack; return error.stack
} }
if (_.isEmpty(error)) { if (_.isEmpty(error)) {
return ''; return ''
} }
const INDENTATION_SPACES = 2; const INDENTATION_SPACES = 2
return JSON.stringify(error, null, INDENTATION_SPACES); return JSON.stringify(error, null, INDENTATION_SPACES)
}; }
/** /**
* @summary Create an error * @summary Create an error
@ -247,18 +247,18 @@ exports.getDescription = (error, options = {}) => {
*/ */
exports.createError = (options) => { exports.createError = (options) => {
if (isBlank(options.title)) { if (isBlank(options.title)) {
throw new Error(`Invalid error title: ${options.title}`); throw new Error(`Invalid error title: ${options.title}`)
} }
const error = new Error(options.title); const error = new Error(options.title)
error.description = options.description; error.description = options.description
if (!_.isNil(options.report) && !options.report) { if (!_.isNil(options.report) && !options.report) {
error.report = false; error.report = false
} }
return error; return error
}; }
/** /**
* @summary Create a user error * @summary Create a user error
@ -289,8 +289,8 @@ exports.createUserError = (options) => {
title: options.title, title: options.title,
description: options.description, description: options.description,
report: false report: false
}); })
}; }
/** /**
* @summary Check if an error is an user error * @summary Check if an error is an user error
@ -308,8 +308,8 @@ exports.createUserError = (options) => {
* } * }
*/ */
exports.isUserError = (error) => { exports.isUserError = (error) => {
return _.isNil(error.report) ? false : !error.report; return _.isNil(error.report) ? false : !error.report
}; }
/** /**
* @summary Convert an Error object to a JSON object * @summary Convert an Error object to a JSON object
@ -327,8 +327,8 @@ exports.isUserError = (error) => {
*/ */
exports.toJSON = (error) => { exports.toJSON = (error) => {
// Handle string error objects to be on the safe side // Handle string error objects to be on the safe side
const isErrorLike = _.isError(error) || _.isPlainObject(error); const isErrorLike = _.isError(error) || _.isPlainObject(error)
const errorObject = isErrorLike ? error : new Error(error); const errorObject = isErrorLike ? error : new Error(error)
return { return {
message: errorObject.message, message: errorObject.message,
@ -336,8 +336,8 @@ exports.toJSON = (error) => {
stack: errorObject.stack, stack: errorObject.stack,
report: errorObject.report, report: errorObject.report,
code: errorObject.code code: errorObject.code
}; }
}; }
/** /**
* @summary Convert a JSON object to an Error object * @summary Convert a JSON object to an Error object
@ -354,5 +354,5 @@ exports.toJSON = (error) => {
* > 'foo' * > 'foo'
*/ */
exports.fromJSON = (json) => { exports.fromJSON = (json) => {
return _.assign(new Error(json.message), json); return _.assign(new Error(json.message), json)
}; }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
/** /**
* @summary Etcher exit codes * @summary Etcher exit codes
@ -63,4 +63,4 @@ module.exports = {
*/ */
CANCELLED: 3 CANCELLED: 3
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @summary Get the extensions of a file * @summary Get the extensions of a file
@ -36,8 +36,8 @@ exports.getFileExtensions = _.memoize((filePath) => {
.split('.') .split('.')
.tail() .tail()
.map(_.toLower) .map(_.toLower)
.value(); .value()
}); })
/** /**
* @summary Get the last file extension * @summary Get the last file extension
@ -53,8 +53,8 @@ exports.getFileExtensions = _.memoize((filePath) => {
* > [ 'gz' ] * > [ 'gz' ]
*/ */
exports.getLastFileExtension = (filePath) => { exports.getLastFileExtension = (filePath) => {
return _.last(exports.getFileExtensions(filePath)); return _.last(exports.getFileExtensions(filePath))
}; }
/** /**
* @summary Get the penultimate file extension * @summary Get the penultimate file extension
@ -70,5 +70,5 @@ exports.getLastFileExtension = (filePath) => {
* > [ 'img' ] * > [ 'img' ]
*/ */
exports.getPenultimateFileExtension = (filePath) => { exports.getPenultimateFileExtension = (filePath) => {
return _.last(_.initial(exports.getFileExtensions(filePath))); return _.last(_.initial(exports.getFileExtensions(filePath)))
}; }

View File

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
/** /**
* @summary Application messages * @summary Application messages
@ -119,4 +119,4 @@ module.exports = {
} }
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const store = require('../store'); const store = require('../store')
/** /**
* @summary Check if there are available drives * @summary Check if there are available drives
@ -32,8 +32,8 @@ const store = require('../store');
* } * }
*/ */
exports.hasAvailableDrives = () => { exports.hasAvailableDrives = () => {
return !_.isEmpty(exports.getDrives()); return !_.isEmpty(exports.getDrives())
}; }
/** /**
* @summary Set a list of drives * @summary Set a list of drives
@ -52,8 +52,8 @@ exports.setDrives = (drives) => {
store.dispatch({ store.dispatch({
type: store.Actions.SET_AVAILABLE_DRIVES, type: store.Actions.SET_AVAILABLE_DRIVES,
data: drives data: drives
}); })
}; }
/** /**
* @summary Get detected drives * @summary Get detected drives
@ -66,5 +66,5 @@ exports.setDrives = (drives) => {
* const drives = availableDrives.getDrives(); * const drives = availableDrives.getDrives();
*/ */
exports.getDrives = () => { exports.getDrives = () => {
return store.getState().toJS().availableDrives; return store.getState().toJS().availableDrives
}; }

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const store = require('../store'); const store = require('../store')
const units = require('../units'); const units = require('../units')
/** /**
* @summary Reset flash state * @summary Reset flash state
@ -31,8 +31,8 @@ const units = require('../units');
exports.resetState = () => { exports.resetState = () => {
store.dispatch({ store.dispatch({
type: store.Actions.RESET_FLASH_STATE type: store.Actions.RESET_FLASH_STATE
}); })
}; }
/** /**
* @summary Check if currently flashing * @summary Check if currently flashing
@ -47,8 +47,8 @@ exports.resetState = () => {
* } * }
*/ */
exports.isFlashing = () => { exports.isFlashing = () => {
return store.getState().toJS().isFlashing; return store.getState().toJS().isFlashing
}; }
/** /**
* @summary Set the flashing flag * @summary Set the flashing flag
@ -67,8 +67,8 @@ exports.isFlashing = () => {
exports.setFlashingFlag = () => { exports.setFlashingFlag = () => {
store.dispatch({ store.dispatch({
type: store.Actions.SET_FLASHING_FLAG type: store.Actions.SET_FLASHING_FLAG
}); })
}; }
/** /**
* @summary Unset the flashing flag * @summary Unset the flashing flag
@ -92,8 +92,8 @@ exports.unsetFlashingFlag = (results) => {
store.dispatch({ store.dispatch({
type: store.Actions.UNSET_FLASHING_FLAG, type: store.Actions.UNSET_FLASHING_FLAG,
data: results data: results
}); })
}; }
/** /**
* @summary Set the flashing state * @summary Set the flashing state
@ -124,15 +124,15 @@ exports.setProgressState = (state) => {
speed: _.attempt(() => { speed: _.attempt(() => {
if (_.isNumber(state.speed) && !_.isNaN(state.speed)) { if (_.isNumber(state.speed) && !_.isNaN(state.speed)) {
// Preserve only two decimal places // Preserve only two decimal places
const PRECISION = 2; const PRECISION = 2
return _.round(units.bytesToMegabytes(state.speed), PRECISION); return _.round(units.bytesToMegabytes(state.speed), PRECISION)
} }
return null; return null
}) })
} }
}); })
}; }
/** /**
* @summary Get the flash results * @summary Get the flash results
@ -145,8 +145,8 @@ exports.setProgressState = (state) => {
* const results = flashState.getFlashResults(); * const results = flashState.getFlashResults();
*/ */
exports.getFlashResults = () => { exports.getFlashResults = () => {
return store.getState().toJS().flashResults; return store.getState().toJS().flashResults
}; }
/** /**
* @summary Get the current flash state * @summary Get the current flash state
@ -159,8 +159,8 @@ exports.getFlashResults = () => {
* const flashState = flashState.getFlashState(); * const flashState = flashState.getFlashState();
*/ */
exports.getFlashState = () => { exports.getFlashState = () => {
return store.getState().get('flashState').toJS(); return store.getState().get('flashState').toJS()
}; }
/** /**
* @summary Determine if the last flash was cancelled * @summary Determine if the last flash was cancelled
@ -178,8 +178,8 @@ exports.getFlashState = () => {
* } * }
*/ */
exports.wasLastFlashCancelled = () => { exports.wasLastFlashCancelled = () => {
return _.get(exports.getFlashResults(), [ 'cancelled' ], false); return _.get(exports.getFlashResults(), [ 'cancelled' ], false)
}; }
/** /**
* @summary Get last flash source checksum * @summary Get last flash source checksum
@ -195,8 +195,8 @@ exports.wasLastFlashCancelled = () => {
* const checksum = flashState.getLastFlashSourceChecksum(); * const checksum = flashState.getLastFlashSourceChecksum();
*/ */
exports.getLastFlashSourceChecksum = () => { exports.getLastFlashSourceChecksum = () => {
return exports.getFlashResults().sourceChecksum; return exports.getFlashResults().sourceChecksum
}; }
/** /**
* @summary Get last flash error code * @summary Get last flash error code
@ -212,8 +212,8 @@ exports.getLastFlashSourceChecksum = () => {
* const errorCode = flashState.getLastFlashErrorCode(); * const errorCode = flashState.getLastFlashErrorCode();
*/ */
exports.getLastFlashErrorCode = () => { exports.getLastFlashErrorCode = () => {
return exports.getFlashResults().errorCode; return exports.getFlashResults().errorCode
}; }
/** /**
* @summary Get current (or last) flash uuid * @summary Get current (or last) flash uuid
@ -229,5 +229,5 @@ exports.getLastFlashErrorCode = () => {
* const uuid = flashState.getFlashUuid(); * const uuid = flashState.getFlashUuid();
*/ */
exports.getFlashUuid = () => { exports.getFlashUuid = () => {
return store.getState().toJS().flashUuid; return store.getState().toJS().flashUuid
}; }

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const store = require('../store'); const store = require('../store')
const availableDrives = require('./available-drives'); const availableDrives = require('./available-drives')
/** /**
* @summary Set a drive * @summary Set a drive
@ -34,8 +34,8 @@ exports.setDrive = (drive) => {
store.dispatch({ store.dispatch({
type: store.Actions.SELECT_DRIVE, type: store.Actions.SELECT_DRIVE,
data: drive data: drive
}); })
}; }
/** /**
* @summary Toggle set drive * @summary Toggle set drive
@ -49,11 +49,11 @@ exports.setDrive = (drive) => {
*/ */
exports.toggleSetDrive = (drive) => { exports.toggleSetDrive = (drive) => {
if (exports.isCurrentDrive(drive)) { if (exports.isCurrentDrive(drive)) {
exports.removeDrive(); exports.removeDrive()
} else { } else {
exports.setDrive(drive); exports.setDrive(drive)
} }
}; }
/** /**
* @summary Set a image * @summary Set a image
@ -71,8 +71,8 @@ exports.setImage = (image) => {
store.dispatch({ store.dispatch({
type: store.Actions.SELECT_IMAGE, type: store.Actions.SELECT_IMAGE,
data: image data: image
}); })
}; }
/** /**
* @summary Get drive * @summary Get drive
@ -87,8 +87,8 @@ exports.setImage = (image) => {
exports.getDrive = () => { exports.getDrive = () => {
return _.find(availableDrives.getDrives(), { return _.find(availableDrives.getDrives(), {
device: store.getState().getIn([ 'selection', 'drive' ]) device: store.getState().getIn([ 'selection', 'drive' ])
}); })
}; }
/** /**
* @summary Get the selected image * @summary Get the selected image
@ -101,8 +101,8 @@ exports.getDrive = () => {
* const image = selectionState.getImage(); * const image = selectionState.getImage();
*/ */
exports.getImage = () => { exports.getImage = () => {
return _.get(store.getState().toJS(), [ 'selection', 'image' ]); return _.get(store.getState().toJS(), [ 'selection', 'image' ])
}; }
/** /**
* @summary Get image path * @summary Get image path
@ -119,8 +119,8 @@ exports.getImagePath = () => {
'selection', 'selection',
'image', 'image',
'path' 'path'
]); ])
}; }
/** /**
* @summary Get image size * @summary Get image size
@ -139,8 +139,8 @@ exports.getImageSize = () => {
'size', 'size',
'final', 'final',
'value' 'value'
]); ])
}; }
/** /**
* @summary Get image url * @summary Get image url
@ -157,8 +157,8 @@ exports.getImageUrl = () => {
'selection', 'selection',
'image', 'image',
'url' 'url'
]); ])
}; }
/** /**
* @summary Get image name * @summary Get image name
@ -175,8 +175,8 @@ exports.getImageName = () => {
'selection', 'selection',
'image', 'image',
'name' 'name'
]); ])
}; }
/** /**
* @summary Get image logo * @summary Get image logo
@ -193,8 +193,8 @@ exports.getImageLogo = () => {
'selection', 'selection',
'image', 'image',
'logo' 'logo'
]); ])
}; }
/** /**
* @summary Get image support url * @summary Get image support url
@ -211,8 +211,8 @@ exports.getImageSupportUrl = () => {
'selection', 'selection',
'image', 'image',
'supportUrl' 'supportUrl'
]); ])
}; }
/** /**
* @summary Get image recommended drive size * @summary Get image recommended drive size
@ -229,8 +229,8 @@ exports.getImageRecommendedDriveSize = () => {
'selection', 'selection',
'image', 'image',
'recommendedDriveSize' 'recommendedDriveSize'
]); ])
}; }
/** /**
* @summary Check if there is a selected drive * @summary Check if there is a selected drive
@ -245,8 +245,8 @@ exports.getImageRecommendedDriveSize = () => {
* } * }
*/ */
exports.hasDrive = () => { exports.hasDrive = () => {
return Boolean(exports.getDrive()); return Boolean(exports.getDrive())
}; }
/** /**
* @summary Check if there is a selected image * @summary Check if there is a selected image
@ -261,8 +261,8 @@ exports.hasDrive = () => {
* } * }
*/ */
exports.hasImage = () => { exports.hasImage = () => {
return Boolean(exports.getImage()); return Boolean(exports.getImage())
}; }
/** /**
* @summary Remove drive * @summary Remove drive
@ -275,8 +275,8 @@ exports.hasImage = () => {
exports.removeDrive = () => { exports.removeDrive = () => {
store.dispatch({ store.dispatch({
type: store.Actions.REMOVE_DRIVE type: store.Actions.REMOVE_DRIVE
}); })
}; }
/** /**
* @summary Remove image * @summary Remove image
@ -289,8 +289,8 @@ exports.removeDrive = () => {
exports.removeImage = () => { exports.removeImage = () => {
store.dispatch({ store.dispatch({
type: store.Actions.REMOVE_IMAGE type: store.Actions.REMOVE_IMAGE
}); })
}; }
/** /**
* @summary Clear selections * @summary Clear selections
@ -310,13 +310,13 @@ exports.clear = (options = {}) => {
if (!options.preserveImage) { if (!options.preserveImage) {
store.dispatch({ store.dispatch({
type: store.Actions.REMOVE_IMAGE type: store.Actions.REMOVE_IMAGE
}); })
} }
store.dispatch({ store.dispatch({
type: store.Actions.REMOVE_DRIVE type: store.Actions.REMOVE_DRIVE
}); })
}; }
/** /**
* @summary Check if a drive is the current drive * @summary Check if a drive is the current drive
@ -333,8 +333,8 @@ exports.clear = (options = {}) => {
*/ */
exports.isCurrentDrive = (drive) => { exports.isCurrentDrive = (drive) => {
if (!drive) { if (!drive) {
return false; return false
} }
return drive === _.get(exports.getDrive(), [ 'device' ]); return drive === _.get(exports.getDrive(), [ 'device' ])
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const path = require('path'); const path = require('path')
const bindings = require('bindings'); const bindings = require('bindings')
/** /**
* @summary Load a native module * @summary Load a native module
@ -40,5 +40,5 @@ exports.load = (moduleName) => {
/* eslint-enable camelcase */ /* eslint-enable camelcase */
}); })
}; }

View File

@ -14,23 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const os = require('os'); const os = require('os')
const nativeModule = require('./native-module'); const nativeModule = require('./native-module')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const childProcess = Bluebird.promisifyAll(require('child_process')); const childProcess = Bluebird.promisifyAll(require('child_process'))
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt')); const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'))
const commandJoin = require('command-join'); const commandJoin = require('command-join')
const _ = require('lodash'); const _ = require('lodash')
const errors = require('./errors'); const errors = require('./errors')
/** /**
* @summary The user id of the UNIX "superuser" * @summary The user id of the UNIX "superuser"
* @constant * @constant
* @type {Number} * @type {Number}
*/ */
const UNIX_SUPERUSER_USER_ID = 0; const UNIX_SUPERUSER_USER_ID = 0
/** /**
* @summary Check if the current process is running with elevated permissions * @summary Check if the current process is running with elevated permissions
@ -64,11 +64,11 @@ exports.isElevated = () => {
.then(_.constant(true)) .then(_.constant(true))
.catch({ .catch({
code: os.constants.errno.EPERM code: os.constants.errno.EPERM
}, _.constant(false)); }, _.constant(false))
} }
return Bluebird.resolve(process.geteuid() === UNIX_SUPERUSER_USER_ID); return Bluebird.resolve(process.geteuid() === UNIX_SUPERUSER_USER_ID)
}; }
/** /**
* @summary Get environment command prefix * @summary Get environment command prefix
@ -87,32 +87,32 @@ exports.isElevated = () => {
* childProcess.execSync(_.join(_.concat(commandPrefix, [ 'mycommand' ]), ' ')); * childProcess.execSync(_.join(_.concat(commandPrefix, [ 'mycommand' ]), ' '));
*/ */
exports.getEnvironmentCommandPrefix = (environment) => { exports.getEnvironmentCommandPrefix = (environment) => {
const isWindows = os.platform() === 'win32'; const isWindows = os.platform() === 'win32'
if (_.isEmpty(environment)) { if (_.isEmpty(environment)) {
return []; return []
} }
const argv = _.flatMap(environment, (value, key) => { const argv = _.flatMap(environment, (value, key) => {
if (_.isNil(value)) { if (_.isNil(value)) {
return []; return []
} }
if (isWindows) { if (isWindows) {
return [ 'set', `${key}=${value}`, '&&' ]; return [ 'set', `${key}=${value}`, '&&' ]
} }
return [ `${key}=${value}` ]; return [ `${key}=${value}` ]
}); })
if (isWindows) { if (isWindows) {
// This is a trick to make the binary afterwards catch // This is a trick to make the binary afterwards catch
// the environment variables set just previously. // the environment variables set just previously.
return _.concat(argv, [ 'call' ]); return _.concat(argv, [ 'call' ])
} }
return _.concat([ 'env' ], argv); return _.concat([ 'env' ], argv)
}; }
/** /**
* @summary Quote a string * @summary Quote a string
@ -126,8 +126,8 @@ exports.getEnvironmentCommandPrefix = (environment) => {
* const result = quote('foo'); * const result = quote('foo');
*/ */
const quoteString = (string) => { const quoteString = (string) => {
return `"${string}"`; return `"${string}"`
}; }
/** /**
* @summary Elevate a command * @summary Elevate a command
@ -154,17 +154,17 @@ const quoteString = (string) => {
* }); * });
*/ */
exports.elevateCommand = (command, options) => { exports.elevateCommand = (command, options) => {
const isWindows = os.platform() === 'win32'; const isWindows = os.platform() === 'win32'
const prefixedCommand = _.concat( const prefixedCommand = _.concat(
exports.getEnvironmentCommandPrefix(options.environment), exports.getEnvironmentCommandPrefix(options.environment),
_.map(command, (string) => { _.map(command, (string) => {
return isWindows ? quoteString(string) : string; return isWindows ? quoteString(string) : string
}) })
); )
if (isWindows) { if (isWindows) {
const elevator = Bluebird.promisifyAll(nativeModule.load('elevator')); const elevator = Bluebird.promisifyAll(nativeModule.load('elevator'))
return elevator.elevateAsync([ return elevator.elevateAsync([
'cmd.exe', 'cmd.exe',
'/c', '/c',
@ -172,8 +172,8 @@ exports.elevateCommand = (command, options) => {
]).then((results) => { ]).then((results) => {
return { return {
cancelled: results.cancelled cancelled: results.cancelled
}; }
}); })
} }
return sudoPrompt.execAsync(commandJoin(prefixedCommand), { return sudoPrompt.execAsync(commandJoin(prefixedCommand), {
@ -182,36 +182,36 @@ exports.elevateCommand = (command, options) => {
if (!_.isEmpty(stderr)) { if (!_.isEmpty(stderr)) {
throw errors.createError({ throw errors.createError({
title: stderr title: stderr
}); })
} }
return { return {
cancelled: false cancelled: false
}; }
// We're hardcoding internal error messages declared by `sudo-prompt`. // We're hardcoding internal error messages declared by `sudo-prompt`.
// There doesn't seem to be a better way to handle these errors, so // There doesn't seem to be a better way to handle these errors, so
// for now, we should make sure we double check if the error messages // for now, we should make sure we double check if the error messages
// have changed every time we upgrade `sudo-prompt`. // have changed every time we upgrade `sudo-prompt`.
}).catch((error) => { }).catch((error) => {
return _.includes(error.message, 'is not in the sudoers file'); return _.includes(error.message, 'is not in the sudoers file')
}, () => { }, () => {
throw errors.createUserError({ throw errors.createUserError({
title: 'Your user doesn\'t have enough privileges to proceed', title: 'Your user doesn\'t have enough privileges to proceed',
description: 'This application requires sudo privileges to be able to write to drives' description: 'This application requires sudo privileges to be able to write to drives'
}); })
}).catch({ }).catch({
message: 'User did not grant permission.' message: 'User did not grant permission.'
}, () => { }, () => {
return { return {
cancelled: true cancelled: true
}; }
}).catch({ }).catch({
message: 'No polkit authentication agent found.' message: 'No polkit authentication agent found.'
}, () => { }, () => {
throw errors.createUserError({ throw errors.createUserError({
title: 'No polkit authentication agent found', title: 'No polkit authentication agent found',
description: 'Please install a polkit authentication agent for your desktop environment of choice to continue' description: 'Please install a polkit authentication agent for your desktop environment of choice to continue'
}); })
}); })
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const semver = require('semver'); const semver = require('semver')
/** /**
* @summary Application release types * @summary Application release types
@ -50,7 +50,7 @@ exports.RELEASE_TYPE = {
*/ */
UNKNOWN: 'UNKNOWN' UNKNOWN: 'UNKNOWN'
}; }
/** /**
* @summary Get the release type from a version string * @summary Get the release type from a version string
@ -69,21 +69,21 @@ exports.RELEASE_TYPE = {
* } * }
*/ */
exports.getReleaseType = (version) => { exports.getReleaseType = (version) => {
const GIT_HASH_REGEX = /^[0-9a-f]{7,40}$/; const GIT_HASH_REGEX = /^[0-9a-f]{7,40}$/
const buildNumber = _.get(semver.parse(version), [ 'build' ]); const buildNumber = _.get(semver.parse(version), [ 'build' ])
if (!_.isNil(buildNumber)) { if (!_.isNil(buildNumber)) {
if (_.isEmpty(buildNumber)) { if (_.isEmpty(buildNumber)) {
return exports.RELEASE_TYPE.PRODUCTION; return exports.RELEASE_TYPE.PRODUCTION
} }
if (GIT_HASH_REGEX.test(_.first(buildNumber))) { if (GIT_HASH_REGEX.test(_.first(buildNumber))) {
return exports.RELEASE_TYPE.SNAPSHOT; return exports.RELEASE_TYPE.SNAPSHOT
} }
} }
return exports.RELEASE_TYPE.UNKNOWN; return exports.RELEASE_TYPE.UNKNOWN
}; }
/** /**
* @summary Check if a version is a stable release * @summary Check if a version is a stable release
@ -99,5 +99,5 @@ exports.getReleaseType = (version) => {
* } * }
*/ */
exports.isStableRelease = (version) => { exports.isStableRelease = (version) => {
return _.isEmpty(_.get(semver.parse(version), [ 'prerelease' ])); return _.isEmpty(_.get(semver.parse(version), [ 'prerelease' ]))
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const errors = require('../errors'); const errors = require('../errors')
/** /**
* @summary Robot commands * @summary Robot commands
@ -44,7 +44,7 @@ exports.COMMAND = {
*/ */
LOG: 'log' LOG: 'log'
}; }
/** /**
* @summary Check whether we should emit parseable output * @summary Check whether we should emit parseable output
@ -60,9 +60,9 @@ exports.COMMAND = {
* } * }
*/ */
exports.isEnabled = (environment) => { exports.isEnabled = (environment) => {
const value = _.get(environment, [ 'ETCHER_CLI_ROBOT' ], false); const value = _.get(environment, [ 'ETCHER_CLI_ROBOT' ], false)
return Boolean(value === 'false' ? false : value); return Boolean(value === 'false' ? false : value)
}; }
/** /**
* @summary Build a machine-parseable message * @summary Build a machine-parseable message
@ -85,14 +85,14 @@ exports.buildMessage = (title, data = {}) => {
if (!_.isPlainObject(data)) { if (!_.isPlainObject(data)) {
throw errors.createError({ throw errors.createError({
title: `Invalid data: ${data}` title: `Invalid data: ${data}`
}); })
} }
return JSON.stringify({ return JSON.stringify({
command: title, command: title,
data data
}); })
}; }
/** /**
* @summary Check whether a string is a robot message * @summary Check whether a string is a robot message
@ -116,11 +116,11 @@ exports.buildMessage = (title, data = {}) => {
*/ */
exports.isMessage = (string) => { exports.isMessage = (string) => {
try { try {
return _.isPlainObject(JSON.parse(string)); return _.isPlainObject(JSON.parse(string))
} catch (error) { } catch (error) {
return false; return false
} }
}; }
/** /**
* @summary Parse a machine-parseable message * @summary Parse a machine-parseable message
@ -141,26 +141,26 @@ exports.isMessage = (string) => {
* > } * > }
*/ */
exports.parseMessage = (string) => { exports.parseMessage = (string) => {
let output = null; let output = null
try { try {
output = JSON.parse(string); output = JSON.parse(string)
} catch (error) { } catch (error) {
throw errors.createError({ throw errors.createError({
title: 'Invalid message', title: 'Invalid message',
description: `${string}, ${error.message}` description: `${string}, ${error.message}`
}); })
} }
if (!output.command || !output.data) { if (!output.command || !output.data) {
throw errors.createError({ throw errors.createError({
title: 'Invalid message', title: 'Invalid message',
description: `No command or data: ${string}` description: `No command or data: ${string}`
}); })
} }
return output; return output
}; }
/** /**
* @summary Build a machine-parseable error message * @summary Build a machine-parseable error message
@ -181,8 +181,8 @@ exports.parseMessage = (string) => {
* > 'foo' * > 'foo'
*/ */
exports.buildErrorMessage = (error) => { exports.buildErrorMessage = (error) => {
return exports.buildMessage(exports.COMMAND.ERROR, errors.toJSON(error)); return exports.buildMessage(exports.COMMAND.ERROR, errors.toJSON(error))
}; }
/** /**
* @summary Recompose an error message * @summary Recompose an error message
@ -203,8 +203,8 @@ exports.buildErrorMessage = (error) => {
* > 'foo' * > 'foo'
*/ */
exports.recomposeErrorMessage = (message) => { exports.recomposeErrorMessage = (message) => {
return errors.fromJSON(message.data); return errors.fromJSON(message.data)
}; }
/** /**
* @summary Get message command * @summary Get message command
@ -224,8 +224,8 @@ exports.recomposeErrorMessage = (message) => {
* > 'foo' * > 'foo'
*/ */
exports.getCommand = (message) => { exports.getCommand = (message) => {
return _.get(message, [ 'command' ]); return _.get(message, [ 'command' ])
}; }
/** /**
* @summary Get message data * @summary Get message data
@ -247,8 +247,8 @@ exports.getCommand = (message) => {
* > { foo: 1 } * > { foo: 1 }
*/ */
exports.getData = (message) => { exports.getData = (message) => {
return _.get(message, [ 'data' ], {}); return _.get(message, [ 'data' ], {})
}; }
/** /**
* @summary Print an error in a machine-friendly way * @summary Print an error in a machine-friendly way
@ -261,8 +261,8 @@ exports.getData = (message) => {
* robot.printError(new Error('This is an error')); * robot.printError(new Error('This is an error'));
*/ */
exports.printError = (error) => { exports.printError = (error) => {
console.error(exports.buildErrorMessage(error)); console.error(exports.buildErrorMessage(error))
}; }
/** /**
* @summary Print a message in a machine-friendly way * @summary Print a message in a machine-friendly way
@ -276,8 +276,8 @@ exports.printError = (error) => {
* robot.printMessage('progress', { percentage: 50 }); * robot.printMessage('progress', { percentage: 50 });
*/ */
exports.printMessage = (message, data) => { exports.printMessage = (message, data) => {
console.log(exports.buildMessage(message, data)); console.log(exports.buildMessage(message, data))
}; }
/** /**
* @summary Log a message to the host's console * @summary Log a message to the host's console
@ -290,5 +290,5 @@ exports.printMessage = (message, data) => {
* robot.log({ example: 'data' }); * robot.log({ example: 'data' });
*/ */
exports.log = (data) => { exports.log = (data) => {
exports.printMessage(exports.COMMAND.LOG, data); exports.printMessage(exports.COMMAND.LOG, data)
}; }

View File

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const semver = require('semver'); const semver = require('semver')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const request = Bluebird.promisifyAll(require('request')); const request = Bluebird.promisifyAll(require('request'))
const xml = Bluebird.promisifyAll(require('xml2js')); const xml = Bluebird.promisifyAll(require('xml2js'))
const release = require('./release'); const release = require('./release')
/** /**
* @summary Etcher S3 bucket URLs * @summary Etcher S3 bucket URLs
@ -46,7 +46,7 @@ exports.BUCKET_URL = {
*/ */
SNAPSHOT: 'https://resin-nightly-downloads.s3.amazonaws.com' SNAPSHOT: 'https://resin-nightly-downloads.s3.amazonaws.com'
}; }
/** /**
* @summary Etcher S3 package name * @summary Etcher S3 package name
@ -54,7 +54,7 @@ exports.BUCKET_URL = {
* @private * @private
* @type {String} * @type {String}
*/ */
const S3_PACKAGE_NAME = 'etcher'; const S3_PACKAGE_NAME = 'etcher'
/** /**
* @summary Number of packages per Etcher version * @summary Number of packages per Etcher version
@ -62,7 +62,7 @@ const S3_PACKAGE_NAME = 'etcher';
* @private * @private
* @type {Number} * @type {Number}
*/ */
const NUMBER_OF_PACKAGES = 8; const NUMBER_OF_PACKAGES = 8
/** /**
* @summary Get the correct S3 bucket url from a release type * @summary Get the correct S3 bucket url from a release type
@ -81,15 +81,15 @@ const NUMBER_OF_PACKAGES = 8;
*/ */
exports.getBucketUrlFromReleaseType = (releaseType) => { exports.getBucketUrlFromReleaseType = (releaseType) => {
if (releaseType === release.RELEASE_TYPE.PRODUCTION) { if (releaseType === release.RELEASE_TYPE.PRODUCTION) {
return exports.BUCKET_URL.PRODUCTION; return exports.BUCKET_URL.PRODUCTION
} }
if (releaseType === release.RELEASE_TYPE.SNAPSHOT) { if (releaseType === release.RELEASE_TYPE.SNAPSHOT) {
return exports.BUCKET_URL.SNAPSHOT; return exports.BUCKET_URL.SNAPSHOT
} }
return null; return null
}; }
/** /**
* @summary Get all remote versions from an S3 bucket * @summary Get all remote versions from an S3 bucket
@ -113,7 +113,7 @@ exports.getBucketUrlFromReleaseType = (releaseType) => {
*/ */
exports.getRemoteVersions = _.memoize((bucketUrl) => { exports.getRemoteVersions = _.memoize((bucketUrl) => {
if (_.isNil(bucketUrl)) { if (_.isNil(bucketUrl)) {
return Bluebird.reject(new Error(`Invalid bucket url: ${bucketUrl}`)); return Bluebird.reject(new Error(`Invalid bucket url: ${bucketUrl}`))
} }
/* eslint-disable lodash/prefer-lodash-method */ /* eslint-disable lodash/prefer-lodash-method */
@ -126,25 +126,25 @@ exports.getRemoteVersions = _.memoize((bucketUrl) => {
.then(xml.parseStringAsync) .then(xml.parseStringAsync)
.get('ListBucketResult') .get('ListBucketResult')
.then((bucketResult) => { .then((bucketResult) => {
return _.get(bucketResult, [ 'Contents' ], []); return _.get(bucketResult, [ 'Contents' ], [])
}) })
.reduce((accumulator, entry) => { .reduce((accumulator, entry) => {
const [ name, version ] = _.split(_.first(entry.Key), '/'); const [ name, version ] = _.split(_.first(entry.Key), '/')
if (name === S3_PACKAGE_NAME) { if (name === S3_PACKAGE_NAME) {
if (_.isNil(accumulator[version])) { if (_.isNil(accumulator[version])) {
accumulator[version] = 1; accumulator[version] = 1
} else { } else {
accumulator[version] += 1; accumulator[version] += 1
} }
} }
return accumulator; return accumulator
}, []) }, [])
.then((versions) => { .then((versions) => {
return _.keys(_.pickBy(versions, (occurrences) => { return _.keys(_.pickBy(versions, (occurrences) => {
return occurrences >= NUMBER_OF_PACKAGES; return occurrences >= NUMBER_OF_PACKAGES
})); }))
}) })
.catch({ .catch({
code: 'ENOTFOUND' code: 'ENOTFOUND'
@ -164,9 +164,9 @@ exports.getRemoteVersions = _.memoize((bucketUrl) => {
code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
}, () => { }, () => {
return []; return []
}); })
}); })
/** /**
* @summary Check if a version satisfies a semver range * @summary Check if a version satisfies a semver range
@ -191,10 +191,10 @@ const semverSatisfies = (version, range) => {
// As a workaround, we drop the prerelease tags, if any, apply the range // As a workaround, we drop the prerelease tags, if any, apply the range
// on that, and keep using the prerelease tag from then on. // on that, and keep using the prerelease tag from then on.
// See https://github.com/npm/node-semver#prerelease-tags // See https://github.com/npm/node-semver#prerelease-tags
const strippedVersion = `${semver.major(version)}.${semver.minor(version)}.${semver.patch(version)}`; const strippedVersion = `${semver.major(version)}.${semver.minor(version)}.${semver.patch(version)}`
return semver.satisfies(strippedVersion, range); return semver.satisfies(strippedVersion, range)
}; }
/** /**
* @summary Get the latest available version for a given release type * @summary Get the latest available version for a given release type
@ -218,18 +218,18 @@ const semverSatisfies = (version, range) => {
*/ */
exports.getLatestVersion = (releaseType, options = {}) => { exports.getLatestVersion = (releaseType, options = {}) => {
// For manual testing purposes // For manual testing purposes
const ETCHER_FAKE_S3_LATEST_VERSION = process.env.ETCHER_FAKE_S3_LATEST_VERSION; const ETCHER_FAKE_S3_LATEST_VERSION = process.env.ETCHER_FAKE_S3_LATEST_VERSION
if (!_.isNil(ETCHER_FAKE_S3_LATEST_VERSION)) { if (!_.isNil(ETCHER_FAKE_S3_LATEST_VERSION)) {
if (release.getReleaseType(ETCHER_FAKE_S3_LATEST_VERSION) === releaseType) { if (release.getReleaseType(ETCHER_FAKE_S3_LATEST_VERSION) === releaseType) {
return Bluebird.resolve(ETCHER_FAKE_S3_LATEST_VERSION); return Bluebird.resolve(ETCHER_FAKE_S3_LATEST_VERSION)
} }
return Bluebird.resolve(); return Bluebird.resolve()
} }
const bucketUrl = exports.getBucketUrlFromReleaseType(releaseType); const bucketUrl = exports.getBucketUrlFromReleaseType(releaseType)
if (_.isNil(bucketUrl)) { if (_.isNil(bucketUrl)) {
return Bluebird.reject(new Error(`No bucket URL found for release type: ${releaseType}`)); return Bluebird.reject(new Error(`No bucket URL found for release type: ${releaseType}`))
} }
/* eslint-disable lodash/prefer-lodash-method */ /* eslint-disable lodash/prefer-lodash-method */
@ -243,11 +243,11 @@ exports.getLatestVersion = (releaseType, options = {}) => {
!release.isStableRelease(version) && !options.includeUnstableChannel !release.isStableRelease(version) && !options.includeUnstableChannel
])) { ])) {
return false; return false
} }
return semverSatisfies(version, options.range || '*'); return semverSatisfies(version, options.range || '*')
}).then((versions) => { }).then((versions) => {
return _.last(versions.sort(semver.compare)); return _.last(versions.sort(semver.compare))
}); })
}; }

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const Immutable = require('immutable'); const Immutable = require('immutable')
const _ = require('lodash'); const _ = require('lodash')
const redux = require('redux'); const redux = require('redux')
const uuidV4 = require('uuid/v4'); const uuidV4 = require('uuid/v4')
const constraints = require('./drive-constraints'); const constraints = require('./drive-constraints')
const supportedFormats = require('./supported-formats'); const supportedFormats = require('./supported-formats')
const errors = require('./errors'); const errors = require('./errors')
const release = require('./release'); const release = require('./release')
const fileExtensions = require('./file-extensions'); const fileExtensions = require('./file-extensions')
const utils = require('./utils'); const utils = require('./utils')
const packageJSON = require('../../package.json'); const packageJSON = require('../../package.json')
/** /**
* @summary Application default state * @summary Application default state
@ -53,7 +53,7 @@ const DEFAULT_STATE = Immutable.fromJS({
lastSleptUpdateNotifier: null, lastSleptUpdateNotifier: null,
lastSleptUpdateNotifierVersion: null lastSleptUpdateNotifierVersion: null
} }
}); })
/** /**
* @summary Application supported action messages * @summary Application supported action messages
@ -72,8 +72,8 @@ const ACTIONS = _.fromPairs(_.map([
'REMOVE_IMAGE', 'REMOVE_IMAGE',
'SET_SETTINGS' 'SET_SETTINGS'
], (message) => { ], (message) => {
return [ message, message ]; return [ message, message ]
})); }))
/** /**
* @summary Find a drive from the list of available drives * @summary Find a drive from the list of available drives
@ -91,11 +91,11 @@ const findDrive = (state, device) => {
/* eslint-disable lodash/prefer-lodash-method */ /* eslint-disable lodash/prefer-lodash-method */
return state.get('availableDrives').find((drive) => { return state.get('availableDrives').find((drive) => {
return drive.get('device') === device; return drive.get('device') === device
}); })
/* eslint-enable lodash/prefer-lodash-method */ /* eslint-enable lodash/prefer-lodash-method */
}; }
/** /**
* @summary The redux store reducer * @summary The redux store reducer
@ -117,25 +117,25 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
if (!action.data) { if (!action.data) {
throw errors.createError({ throw errors.createError({
title: 'Missing drives' title: 'Missing drives'
}); })
} }
if (!_.isArray(action.data) || !_.every(action.data, _.isPlainObject)) { if (!_.isArray(action.data) || !_.every(action.data, _.isPlainObject)) {
throw errors.createError({ throw errors.createError({
title: `Invalid drives: ${action.data}` title: `Invalid drives: ${action.data}`
}); })
} }
const newState = state.set('availableDrives', Immutable.fromJS(action.data)); const newState = state.set('availableDrives', Immutable.fromJS(action.data))
const AUTOSELECT_DRIVE_COUNT = 1; const AUTOSELECT_DRIVE_COUNT = 1
const numberOfDrives = action.data.length; const numberOfDrives = action.data.length
if (numberOfDrives === AUTOSELECT_DRIVE_COUNT) { if (numberOfDrives === AUTOSELECT_DRIVE_COUNT) {
const drive = _.first(action.data); const drive = _.first(action.data)
// Even if there's no image selected, we need to call several // Even if there's no image selected, we need to call several
// drive/image related checks, and `{}` works fine with them // drive/image related checks, and `{}` works fine with them
const image = state.getIn([ 'selection', 'image' ], Immutable.fromJS({})).toJS(); const image = state.getIn([ 'selection', 'image' ], Immutable.fromJS({})).toJS()
if (_.every([ if (_.every([
constraints.isDriveValid(drive, image), constraints.isDriveValid(drive, image),
@ -149,73 +149,73 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
return storeReducer(newState, { return storeReducer(newState, {
type: ACTIONS.SELECT_DRIVE, type: ACTIONS.SELECT_DRIVE,
data: drive.device data: drive.device
}); })
} }
} }
const selectedDevice = newState.getIn([ 'selection', 'drive' ]); const selectedDevice = newState.getIn([ 'selection', 'drive' ])
if (selectedDevice && !_.find(action.data, { if (selectedDevice && !_.find(action.data, {
device: selectedDevice device: selectedDevice
})) { })) {
return storeReducer(newState, { return storeReducer(newState, {
type: ACTIONS.REMOVE_DRIVE type: ACTIONS.REMOVE_DRIVE
}); })
} }
return newState; return newState
} }
case ACTIONS.SET_FLASH_STATE: { case ACTIONS.SET_FLASH_STATE: {
if (!state.get('isFlashing')) { if (!state.get('isFlashing')) {
throw errors.createError({ throw errors.createError({
title: 'Can\'t set the flashing state when not flashing' title: 'Can\'t set the flashing state when not flashing'
}); })
} }
if (!action.data.type) { if (!action.data.type) {
throw errors.createError({ throw errors.createError({
title: 'Missing state type' title: 'Missing state type'
}); })
} }
if (!_.isString(action.data.type)) { if (!_.isString(action.data.type)) {
throw errors.createError({ throw errors.createError({
title: `Invalid state type: ${action.data.type}` title: `Invalid state type: ${action.data.type}`
}); })
} }
if (_.isNil(action.data.percentage)) { if (_.isNil(action.data.percentage)) {
throw errors.createError({ throw errors.createError({
title: 'Missing state percentage' title: 'Missing state percentage'
}); })
} }
if (!utils.isValidPercentage(action.data.percentage)) { if (!utils.isValidPercentage(action.data.percentage)) {
throw errors.createError({ throw errors.createError({
title: `Invalid state percentage: ${action.data.percentage}` title: `Invalid state percentage: ${action.data.percentage}`
}); })
} }
if (_.isNil(action.data.eta)) { if (_.isNil(action.data.eta)) {
throw errors.createError({ throw errors.createError({
title: 'Missing state eta' title: 'Missing state eta'
}); })
} }
if (!_.isNumber(action.data.eta)) { if (!_.isNumber(action.data.eta)) {
throw errors.createError({ throw errors.createError({
title: `Invalid state eta: ${action.data.eta}` title: `Invalid state eta: ${action.data.eta}`
}); })
} }
if (_.isNil(action.data.speed)) { if (_.isNil(action.data.speed)) {
throw errors.createError({ throw errors.createError({
title: 'Missing state speed' title: 'Missing state speed'
}); })
} }
return state.set('flashState', Immutable.fromJS(action.data)); return state.set('flashState', Immutable.fromJS(action.data))
} }
case ACTIONS.RESET_FLASH_STATE: { case ACTIONS.RESET_FLASH_STATE: {
@ -223,111 +223,111 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
.set('isFlashing', false) .set('isFlashing', false)
.set('flashState', DEFAULT_STATE.get('flashState')) .set('flashState', DEFAULT_STATE.get('flashState'))
.set('flashResults', DEFAULT_STATE.get('flashResults')) .set('flashResults', DEFAULT_STATE.get('flashResults'))
.delete('flashUuid'); .delete('flashUuid')
} }
case ACTIONS.SET_FLASHING_FLAG: { case ACTIONS.SET_FLASHING_FLAG: {
return state return state
.set('isFlashing', true) .set('isFlashing', true)
.set('flashUuid', uuidV4()) .set('flashUuid', uuidV4())
.set('flashResults', DEFAULT_STATE.get('flashResults')); .set('flashResults', DEFAULT_STATE.get('flashResults'))
} }
case ACTIONS.UNSET_FLASHING_FLAG: { case ACTIONS.UNSET_FLASHING_FLAG: {
if (!action.data) { if (!action.data) {
throw errors.createError({ throw errors.createError({
title: 'Missing results' title: 'Missing results'
}); })
} }
_.defaults(action.data, { _.defaults(action.data, {
cancelled: false cancelled: false
}); })
if (!_.isBoolean(action.data.cancelled)) { if (!_.isBoolean(action.data.cancelled)) {
throw errors.createError({ throw errors.createError({
title: `Invalid results cancelled: ${action.data.cancelled}` title: `Invalid results cancelled: ${action.data.cancelled}`
}); })
} }
if (action.data.cancelled && action.data.sourceChecksum) { if (action.data.cancelled && action.data.sourceChecksum) {
throw errors.createError({ throw errors.createError({
title: 'The sourceChecksum value can\'t exist if the flashing was cancelled' title: 'The sourceChecksum value can\'t exist if the flashing was cancelled'
}); })
} }
if (action.data.sourceChecksum && !_.isString(action.data.sourceChecksum)) { if (action.data.sourceChecksum && !_.isString(action.data.sourceChecksum)) {
throw errors.createError({ throw errors.createError({
title: `Invalid results sourceChecksum: ${action.data.sourceChecksum}` title: `Invalid results sourceChecksum: ${action.data.sourceChecksum}`
}); })
} }
if (action.data.errorCode && !_.isString(action.data.errorCode) && !_.isNumber(action.data.errorCode)) { if (action.data.errorCode && !_.isString(action.data.errorCode) && !_.isNumber(action.data.errorCode)) {
throw errors.createError({ throw errors.createError({
title: `Invalid results errorCode: ${action.data.errorCode}` title: `Invalid results errorCode: ${action.data.errorCode}`
}); })
} }
return state return state
.set('isFlashing', false) .set('isFlashing', false)
.set('flashResults', Immutable.fromJS(action.data)) .set('flashResults', Immutable.fromJS(action.data))
.set('flashState', DEFAULT_STATE.get('flashState')); .set('flashState', DEFAULT_STATE.get('flashState'))
} }
case ACTIONS.SELECT_DRIVE: { case ACTIONS.SELECT_DRIVE: {
if (!action.data) { if (!action.data) {
throw errors.createError({ throw errors.createError({
title: 'Missing drive' title: 'Missing drive'
}); })
} }
if (!_.isString(action.data)) { if (!_.isString(action.data)) {
throw errors.createError({ throw errors.createError({
title: `Invalid drive: ${action.data}` title: `Invalid drive: ${action.data}`
}); })
} }
const selectedDrive = findDrive(state, action.data); const selectedDrive = findDrive(state, action.data)
if (!selectedDrive) { if (!selectedDrive) {
throw errors.createError({ throw errors.createError({
title: `The drive is not available: ${action.data}` title: `The drive is not available: ${action.data}`
}); })
} }
if (selectedDrive.get('protected')) { if (selectedDrive.get('protected')) {
throw errors.createError({ throw errors.createError({
title: 'The drive is write-protected' title: 'The drive is write-protected'
}); })
} }
const image = state.getIn([ 'selection', 'image' ]); const image = state.getIn([ 'selection', 'image' ])
if (image && !constraints.isDriveLargeEnough(selectedDrive.toJS(), image.toJS())) { if (image && !constraints.isDriveLargeEnough(selectedDrive.toJS(), image.toJS())) {
throw errors.createError({ throw errors.createError({
title: 'The drive is not large enough' title: 'The drive is not large enough'
}); })
} }
return state.setIn([ 'selection', 'drive' ], Immutable.fromJS(action.data)); return state.setIn([ 'selection', 'drive' ], Immutable.fromJS(action.data))
} }
case ACTIONS.SELECT_IMAGE: { case ACTIONS.SELECT_IMAGE: {
if (!action.data.path) { if (!action.data.path) {
throw errors.createError({ throw errors.createError({
title: 'Missing image path' title: 'Missing image path'
}); })
} }
if (!_.isString(action.data.path)) { if (!_.isString(action.data.path)) {
throw errors.createError({ throw errors.createError({
title: `Invalid image path: ${action.data.path}` title: `Invalid image path: ${action.data.path}`
}); })
} }
if (!action.data.extension) { if (!action.data.extension) {
throw errors.createError({ throw errors.createError({
title: 'Missing image extension' title: 'Missing image extension'
}); })
} }
if (_.some([ if (_.some([
@ -336,16 +336,16 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
])) { ])) {
throw errors.createError({ throw errors.createError({
title: `Invalid image extension: ${action.data.extension}` title: `Invalid image extension: ${action.data.extension}`
}); })
} }
const lastImageExtension = fileExtensions.getLastFileExtension(action.data.path); const lastImageExtension = fileExtensions.getLastFileExtension(action.data.path)
if (lastImageExtension !== action.data.extension) { if (lastImageExtension !== action.data.extension) {
if (!action.data.archiveExtension) { if (!action.data.archiveExtension) {
throw errors.createError({ throw errors.createError({
title: 'Missing image archive extension' title: 'Missing image archive extension'
}); })
} }
if (_.some([ if (_.some([
@ -354,67 +354,67 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
])) { ])) {
throw errors.createError({ throw errors.createError({
title: `Invalid image archive extension: ${action.data.archiveExtension}` title: `Invalid image archive extension: ${action.data.archiveExtension}`
}); })
} }
if (lastImageExtension !== action.data.archiveExtension) { if (lastImageExtension !== action.data.archiveExtension) {
throw errors.createError({ throw errors.createError({
title: `Image archive extension mismatch: ${action.data.archiveExtension} and ${lastImageExtension}` title: `Image archive extension mismatch: ${action.data.archiveExtension} and ${lastImageExtension}`
}); })
} }
} }
if (!action.data.size) { if (!action.data.size) {
throw errors.createError({ throw errors.createError({
title: 'Missing image size' title: 'Missing image size'
}); })
} }
if (!_.isPlainObject(action.data.size)) { if (!_.isPlainObject(action.data.size)) {
throw errors.createError({ throw errors.createError({
title: `Invalid image size: ${action.data.size}` title: `Invalid image size: ${action.data.size}`
}); })
} }
const MINIMUM_IMAGE_SIZE = 0; const MINIMUM_IMAGE_SIZE = 0
if (!_.isInteger(action.data.size.original) || action.data.size.original < MINIMUM_IMAGE_SIZE) { if (!_.isInteger(action.data.size.original) || action.data.size.original < MINIMUM_IMAGE_SIZE) {
throw errors.createError({ throw errors.createError({
title: `Invalid original image size: ${action.data.size.original}` title: `Invalid original image size: ${action.data.size.original}`
}); })
} }
if (!_.isInteger(action.data.size.final.value) || action.data.size.final.value < MINIMUM_IMAGE_SIZE) { if (!_.isInteger(action.data.size.final.value) || action.data.size.final.value < MINIMUM_IMAGE_SIZE) {
throw errors.createError({ throw errors.createError({
title: `Invalid final image size: ${action.data.size.final.value}` title: `Invalid final image size: ${action.data.size.final.value}`
}); })
} }
if (!_.isBoolean(action.data.size.final.estimation)) { if (!_.isBoolean(action.data.size.final.estimation)) {
throw errors.createError({ throw errors.createError({
title: `Invalid final image size estimation flag: ${action.data.size.final.estimation}` title: `Invalid final image size estimation flag: ${action.data.size.final.estimation}`
}); })
} }
if (action.data.url && !_.isString(action.data.url)) { if (action.data.url && !_.isString(action.data.url)) {
throw errors.createError({ throw errors.createError({
title: `Invalid image url: ${action.data.url}` title: `Invalid image url: ${action.data.url}`
}); })
} }
if (action.data.name && !_.isString(action.data.name)) { if (action.data.name && !_.isString(action.data.name)) {
throw errors.createError({ throw errors.createError({
title: `Invalid image name: ${action.data.name}` title: `Invalid image name: ${action.data.name}`
}); })
} }
if (action.data.logo && !_.isString(action.data.logo)) { if (action.data.logo && !_.isString(action.data.logo)) {
throw errors.createError({ throw errors.createError({
title: `Invalid image logo: ${action.data.logo}` title: `Invalid image logo: ${action.data.logo}`
}); })
} }
const selectedDrive = findDrive(state, state.getIn([ 'selection', 'drive' ])); const selectedDrive = findDrive(state, state.getIn([ 'selection', 'drive' ]))
return _.attempt(() => { return _.attempt(() => {
if (selectedDrive && !_.every([ if (selectedDrive && !_.every([
@ -423,64 +423,64 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
])) { ])) {
return storeReducer(state, { return storeReducer(state, {
type: ACTIONS.REMOVE_DRIVE type: ACTIONS.REMOVE_DRIVE
}); })
} }
return state; return state
}).setIn([ 'selection', 'image' ], Immutable.fromJS(action.data)); }).setIn([ 'selection', 'image' ], Immutable.fromJS(action.data))
} }
case ACTIONS.REMOVE_DRIVE: { case ACTIONS.REMOVE_DRIVE: {
return state.deleteIn([ 'selection', 'drive' ]); return state.deleteIn([ 'selection', 'drive' ])
} }
case ACTIONS.REMOVE_IMAGE: { case ACTIONS.REMOVE_IMAGE: {
return state.deleteIn([ 'selection', 'image' ]); return state.deleteIn([ 'selection', 'image' ])
} }
case ACTIONS.SET_SETTINGS: { case ACTIONS.SET_SETTINGS: {
if (!action.data) { if (!action.data) {
throw errors.createError({ throw errors.createError({
title: 'Missing settings' title: 'Missing settings'
}); })
} }
if (!_.isPlainObject(action.data)) { if (!_.isPlainObject(action.data)) {
throw errors.createError({ throw errors.createError({
title: `Invalid settings: ${action.data}` title: `Invalid settings: ${action.data}`
}); })
} }
const invalidKey = _.find(_.keys(action.data), (key) => { const invalidKey = _.find(_.keys(action.data), (key) => {
return !_.isString(key); return !_.isString(key)
}); })
if (!_.isNil(invalidKey)) { if (!_.isNil(invalidKey)) {
throw errors.createError({ throw errors.createError({
title: `Invalid setting key: ${invalidKey}` title: `Invalid setting key: ${invalidKey}`
}); })
} }
const invalidPair = _.find(_.toPairs(action.data), (pair) => { const invalidPair = _.find(_.toPairs(action.data), (pair) => {
return _.isObject(_.last(pair)); return _.isObject(_.last(pair))
}); })
if (!_.isNil(invalidPair)) { if (!_.isNil(invalidPair)) {
throw errors.createError({ throw errors.createError({
title: `Invalid setting value: ${_.last(invalidPair)} for ${_.first(invalidPair)}` title: `Invalid setting value: ${_.last(invalidPair)} for ${_.first(invalidPair)}`
}); })
} }
return state.setIn([ 'settings' ], Immutable.fromJS(action.data)); return state.setIn([ 'settings' ], Immutable.fromJS(action.data))
} }
default: { default: {
return state; return state
} }
} }
}; }
module.exports = _.merge(redux.createStore(storeReducer, DEFAULT_STATE), { module.exports = _.merge(redux.createStore(storeReducer, DEFAULT_STATE), {
Actions: ACTIONS, Actions: ACTIONS,
Defaults: DEFAULT_STATE Defaults: DEFAULT_STATE
}); })

View File

@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const path = require('path'); const path = require('path')
const imageStream = require('../image-stream'); const imageStream = require('../image-stream')
const fileExtensions = require('./file-extensions'); const fileExtensions = require('./file-extensions')
/** /**
* @summary Build an extension list getter from a type * @summary Build an extension list getter from a type
@ -36,9 +36,9 @@ const getExtensionsFromTypeGetter = (type) => {
return () => { return () => {
return _.map(_.filter(imageStream.supportedFileTypes, { return _.map(_.filter(imageStream.supportedFileTypes, {
type type
}), 'extension'); }), 'extension')
}; }
}; }
/** /**
* @summary Get compressed extensions * @summary Get compressed extensions
@ -52,7 +52,7 @@ const getExtensionsFromTypeGetter = (type) => {
* console.log('We support the ' + extension + ' compressed file format'); * console.log('We support the ' + extension + ' compressed file format');
* }); * });
*/ */
exports.getCompressedExtensions = getExtensionsFromTypeGetter('compressed'); exports.getCompressedExtensions = getExtensionsFromTypeGetter('compressed')
/** /**
* @summary Get non compressed extensions * @summary Get non compressed extensions
@ -66,7 +66,7 @@ exports.getCompressedExtensions = getExtensionsFromTypeGetter('compressed');
* console.log('We support the ' + extension + ' file format'); * console.log('We support the ' + extension + ' file format');
* }); * });
*/ */
exports.getNonCompressedExtensions = getExtensionsFromTypeGetter('image'); exports.getNonCompressedExtensions = getExtensionsFromTypeGetter('image')
/** /**
* @summary Get archive extensions * @summary Get archive extensions
@ -80,7 +80,7 @@ exports.getNonCompressedExtensions = getExtensionsFromTypeGetter('image');
* console.log('We support the ' + extension + ' file format'); * console.log('We support the ' + extension + ' file format');
* }); * });
*/ */
exports.getArchiveExtensions = getExtensionsFromTypeGetter('archive'); exports.getArchiveExtensions = getExtensionsFromTypeGetter('archive')
/** /**
* @summary Get all supported extensions * @summary Get all supported extensions
@ -95,8 +95,8 @@ exports.getArchiveExtensions = getExtensionsFromTypeGetter('archive');
* }); * });
*/ */
exports.getAllExtensions = () => { exports.getAllExtensions = () => {
return _.map(imageStream.supportedFileTypes, 'extension'); return _.map(imageStream.supportedFileTypes, 'extension')
}; }
/** /**
* @summary Check if an image is supported * @summary Check if an image is supported
@ -112,21 +112,21 @@ exports.getAllExtensions = () => {
* } * }
*/ */
exports.isSupportedImage = (imagePath) => { exports.isSupportedImage = (imagePath) => {
const lastExtension = fileExtensions.getLastFileExtension(imagePath); const lastExtension = fileExtensions.getLastFileExtension(imagePath)
const penultimateExtension = fileExtensions.getPenultimateFileExtension(imagePath); const penultimateExtension = fileExtensions.getPenultimateFileExtension(imagePath)
if (_.some([ if (_.some([
_.includes(exports.getNonCompressedExtensions(), lastExtension), _.includes(exports.getNonCompressedExtensions(), lastExtension),
_.includes(exports.getArchiveExtensions(), lastExtension) _.includes(exports.getArchiveExtensions(), lastExtension)
])) { ])) {
return true; return true
} }
return _.every([ return _.every([
_.includes(exports.getCompressedExtensions(), lastExtension), _.includes(exports.getCompressedExtensions(), lastExtension),
_.includes(exports.getNonCompressedExtensions(), penultimateExtension) _.includes(exports.getNonCompressedExtensions(), penultimateExtension)
]); ])
}; }
/** /**
* @summary Check if an image seems to be a Windows image * @summary Check if an image seems to be a Windows image
@ -142,6 +142,6 @@ exports.isSupportedImage = (imagePath) => {
* } * }
*/ */
exports.looksLikeWindowsImage = (imagePath) => { exports.looksLikeWindowsImage = (imagePath) => {
const regex = /windows|win7|win8|win10|winxp/i; const regex = /windows|win7|win8|win10|winxp/i
return regex.test(path.basename(imagePath)); return regex.test(path.basename(imagePath))
}; }

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const prettyBytes = require('pretty-bytes'); const prettyBytes = require('pretty-bytes')
/** /**
* @summary Megabyte to byte ratio * @summary Megabyte to byte ratio
@ -29,7 +29,7 @@ const prettyBytes = require('pretty-bytes');
* @description * @description
* 1 MB = 1e+6 B * 1 MB = 1e+6 B
*/ */
const MEGABYTE_TO_BYTE_RATIO = 1e+6; const MEGABYTE_TO_BYTE_RATIO = 1e+6
/** /**
* @summary Milliseconds in a day * @summary Milliseconds in a day
@ -40,7 +40,7 @@ const MEGABYTE_TO_BYTE_RATIO = 1e+6;
* @description * @description
* From 24 * 60 * 60 * 1000 * From 24 * 60 * 60 * 1000
*/ */
const MILLISECONDS_IN_A_DAY = 86400000; const MILLISECONDS_IN_A_DAY = 86400000
/** /**
* @summary Convert bytes to megabytes * @summary Convert bytes to megabytes
@ -54,8 +54,8 @@ const MILLISECONDS_IN_A_DAY = 86400000;
* const result = units.bytesToMegabytes(7801405440); * const result = units.bytesToMegabytes(7801405440);
*/ */
exports.bytesToMegabytes = (bytes) => { exports.bytesToMegabytes = (bytes) => {
return bytes / MEGABYTE_TO_BYTE_RATIO; return bytes / MEGABYTE_TO_BYTE_RATIO
}; }
/** /**
* @summary Convert bytes to most appropriate unit string * @summary Convert bytes to most appropriate unit string
@ -71,11 +71,11 @@ exports.bytesToMegabytes = (bytes) => {
*/ */
exports.bytesToClosestUnit = (bytes) => { exports.bytesToClosestUnit = (bytes) => {
if (_.isNumber(bytes)) { if (_.isNumber(bytes)) {
return prettyBytes(bytes); return prettyBytes(bytes)
} }
return null; return null
}; }
/** /**
* @summary Convert days to milliseconds * @summary Convert days to milliseconds
@ -89,5 +89,5 @@ exports.bytesToClosestUnit = (bytes) => {
* const result = units.daysToMilliseconds(2); * const result = units.daysToMilliseconds(2);
*/ */
exports.daysToMilliseconds = (days) => { exports.daysToMilliseconds = (days) => {
return days * MILLISECONDS_IN_A_DAY; return days * MILLISECONDS_IN_A_DAY
}; }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const errors = require('./errors'); const errors = require('./errors')
/** /**
* @summary Minimum percentage value * @summary Minimum percentage value
@ -25,7 +25,7 @@ const errors = require('./errors');
* @public * @public
* @type {Number} * @type {Number}
*/ */
exports.PERCENTAGE_MINIMUM = 0; exports.PERCENTAGE_MINIMUM = 0
/** /**
* @summary Maximum percentage value * @summary Maximum percentage value
@ -33,7 +33,7 @@ exports.PERCENTAGE_MINIMUM = 0;
* @public * @public
* @type {Number} * @type {Number}
*/ */
exports.PERCENTAGE_MAXIMUM = 100; exports.PERCENTAGE_MAXIMUM = 100
/** /**
* @summary Check if a percentage is valid * @summary Check if a percentage is valid
@ -53,8 +53,8 @@ exports.isValidPercentage = (percentage) => {
_.isNumber(percentage), _.isNumber(percentage),
percentage >= exports.PERCENTAGE_MINIMUM, percentage >= exports.PERCENTAGE_MINIMUM,
percentage <= exports.PERCENTAGE_MAXIMUM percentage <= exports.PERCENTAGE_MAXIMUM
]); ])
}; }
/** /**
* @summary Convert a percentage to a float * @summary Convert a percentage to a float
@ -73,8 +73,8 @@ exports.percentageToFloat = (percentage) => {
if (!exports.isValidPercentage(percentage)) { if (!exports.isValidPercentage(percentage)) {
throw errors.createError({ throw errors.createError({
title: `Invalid percentage: ${percentage}` title: `Invalid percentage: ${percentage}`
}); })
} }
return percentage / exports.PERCENTAGE_MAXIMUM; return percentage / exports.PERCENTAGE_MAXIMUM
}; }

View File

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
// Enable debug information from all modules that use `debug` // Enable debug information from all modules that use `debug`
// See https://github.com/visionmedia/debug#browser-support // See https://github.com/visionmedia/debug#browser-support
process.env.DEBUG = '*'; process.env.DEBUG = '*'
// See http://electron.atom.io/docs/v0.37.7/api/environment-variables/#electronrunasnode // See http://electron.atom.io/docs/v0.37.7/api/environment-variables/#electronrunasnode
// //
@ -31,7 +31,7 @@ process.env.DEBUG = '*';
// an older equivalent of `ELECTRON_RUN_AS_NODE` that still gets set when // an older equivalent of `ELECTRON_RUN_AS_NODE` that still gets set when
// using `child_process.fork()`. // using `child_process.fork()`.
if (process.env.ELECTRON_RUN_AS_NODE || process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE) { if (process.env.ELECTRON_RUN_AS_NODE || process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE) {
require('./cli/etcher'); require('./cli/etcher')
} else { } else {
require('./gui/etcher'); require('./gui/etcher')
} }

226
npm-shrinkwrap.json generated
View File

@ -423,9 +423,9 @@
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"js-tokens": { "js-tokens": {
"version": "3.0.1", "version": "3.0.2",
"from": "js-tokens@>=3.0.0 <4.0.0", "from": "js-tokens@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"dev": true "dev": true
} }
} }
@ -954,6 +954,12 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"dev": true "dev": true
}, },
"contains-path": {
"version": "0.1.0",
"from": "contains-path@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
"dev": true
},
"cookie": { "cookie": {
"version": "0.3.1", "version": "0.3.1",
"from": "cookie@0.3.1", "from": "cookie@0.3.1",
@ -1880,9 +1886,9 @@
"dev": true "dev": true
}, },
"eslint": { "eslint": {
"version": "3.18.0", "version": "3.19.0",
"from": "eslint@3.18.0", "from": "eslint@3.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-3.18.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"cli-width": { "cli-width": {
@ -1897,10 +1903,118 @@
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
"dev": true "dev": true
}, },
"json-stable-stringify": { "strip-bom": {
"version": "1.0.1", "version": "3.0.0",
"from": "json-stable-stringify@>=1.0.0 <2.0.0", "from": "strip-bom@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"dev": true
}
}
},
"eslint-config-standard": {
"version": "10.2.1",
"from": "eslint-config-standard@latest",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz",
"dev": true
},
"eslint-import-resolver-node": {
"version": "0.3.1",
"from": "eslint-import-resolver-node@>=0.3.1 <0.4.0",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz",
"dev": true,
"dependencies": {
"debug": {
"version": "2.6.8",
"from": "debug@^2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"dev": true
},
"ms": {
"version": "2.0.0",
"from": "ms@2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"dev": true
}
}
},
"eslint-module-utils": {
"version": "2.1.1",
"from": "eslint-module-utils@>=2.1.1 <3.0.0",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz",
"dev": true,
"dependencies": {
"debug": {
"version": "2.6.8",
"from": "debug@^2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"dev": true
},
"ms": {
"version": "2.0.0",
"from": "ms@2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"dev": true
}
}
},
"eslint-plugin-import": {
"version": "2.7.0",
"from": "eslint-plugin-import@latest",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz",
"dev": true,
"dependencies": {
"debug": {
"version": "2.6.8",
"from": "debug@>=2.6.8 <3.0.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"dev": true
},
"doctrine": {
"version": "1.5.0",
"from": "doctrine@1.5.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
"dev": true
},
"find-up": {
"version": "2.1.0",
"from": "find-up@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"dev": true
},
"isarray": {
"version": "1.0.0",
"from": "isarray@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"dev": true
},
"load-json-file": {
"version": "2.0.0",
"from": "load-json-file@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
"dev": true
},
"ms": {
"version": "2.0.0",
"from": "ms@2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"dev": true
},
"path-type": {
"version": "2.0.0",
"from": "path-type@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
"dev": true
},
"read-pkg": {
"version": "2.0.0",
"from": "read-pkg@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
"dev": true
},
"read-pkg-up": {
"version": "2.0.0",
"from": "read-pkg-up@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
"dev": true "dev": true
}, },
"strip-bom": { "strip-bom": {
@ -1923,6 +2037,56 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.3.6.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-lodash/-/eslint-plugin-lodash-2.3.6.tgz",
"dev": true "dev": true
}, },
"eslint-plugin-node": {
"version": "5.1.1",
"from": "eslint-plugin-node@latest",
"resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.1.1.tgz",
"dev": true,
"dependencies": {
"balanced-match": {
"version": "1.0.0",
"from": "balanced-match@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"dev": true
},
"brace-expansion": {
"version": "1.1.8",
"from": "brace-expansion@>=1.1.7 <2.0.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
"dev": true
},
"ignore": {
"version": "3.3.3",
"from": "ignore@>=3.3.3 <4.0.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"from": "minimatch@>=3.0.4 <4.0.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"dev": true
},
"semver": {
"version": "5.3.0",
"from": "semver@5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"dev": true
}
}
},
"eslint-plugin-promise": {
"version": "3.5.0",
"from": "eslint-plugin-promise@latest",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz",
"dev": true
},
"eslint-plugin-standard": {
"version": "3.0.1",
"from": "eslint-plugin-standard@latest",
"resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz",
"dev": true
},
"espree": { "espree": {
"version": "3.4.0", "version": "3.4.0",
"from": "espree@>=3.4.0 <4.0.0", "from": "espree@>=3.4.0 <4.0.0",
@ -2451,6 +2615,12 @@
} }
} }
}, },
"function-bind": {
"version": "1.1.0",
"from": "function-bind@>=1.0.2 <2.0.0",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz",
"dev": true
},
"gauge": { "gauge": {
"version": "2.7.3", "version": "2.7.3",
"from": "gauge@>=2.7.1 <2.8.0", "from": "gauge@>=2.7.1 <2.8.0",
@ -2724,6 +2894,12 @@
"from": "har-validator@>=4.2.1 <4.3.0", "from": "har-validator@>=4.2.1 <4.3.0",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz" "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz"
}, },
"has": {
"version": "1.0.1",
"from": "has@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
"dev": true
},
"has-ansi": { "has-ansi": {
"version": "2.0.0", "version": "2.0.0",
"from": "has-ansi@>=2.0.0 <3.0.0", "from": "has-ansi@>=2.0.0 <3.0.0",
@ -2954,9 +3130,9 @@
} }
}, },
"interpret": { "interpret": {
"version": "1.0.1", "version": "1.0.3",
"from": "interpret@>=1.0.0 <2.0.0", "from": "interpret@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.1.tgz", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
"dev": true "dev": true
}, },
"invert-kv": { "invert-kv": {
@ -3324,6 +3500,12 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"dev": true "dev": true
}, },
"json-stable-stringify": {
"version": "1.0.1",
"from": "json-stable-stringify@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
"dev": true
},
"json-stringify-safe": { "json-stringify-safe": {
"version": "5.0.1", "version": "5.0.1",
"from": "json-stringify-safe@>=5.0.0 <5.1.0", "from": "json-stringify-safe@>=5.0.0 <5.1.0",
@ -3507,6 +3689,12 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"dev": true "dev": true
}, },
"lodash.cond": {
"version": "4.5.2",
"from": "lodash.cond@>=4.3.0 <5.0.0",
"resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz",
"dev": true
},
"lodash.create": { "lodash.create": {
"version": "3.1.1", "version": "3.1.1",
"from": "lodash.create@3.1.1", "from": "lodash.create@3.1.1",
@ -8237,6 +8425,12 @@
"from": "pkg-conf@>=1.1.2 <2.0.0", "from": "pkg-conf@>=1.1.2 <2.0.0",
"resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz" "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-1.1.3.tgz"
}, },
"pkg-dir": {
"version": "1.0.0",
"from": "pkg-dir@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
"dev": true
},
"pkg-fetch": { "pkg-fetch": {
"version": "2.3.3", "version": "2.3.3",
"from": "pkg-fetch@2.3.3", "from": "pkg-fetch@2.3.3",
@ -8762,9 +8956,9 @@
} }
}, },
"resolve": { "resolve": {
"version": "1.3.2", "version": "1.4.0",
"from": "resolve@>=1.1.4 <2.0.0", "from": "resolve@>=1.1.6 <2.0.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.2.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
"dev": true "dev": true
}, },
"resolve-dir": { "resolve-dir": {
@ -9000,9 +9194,9 @@
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
}, },
"shelljs": { "shelljs": {
"version": "0.7.7", "version": "0.7.8",
"from": "shelljs@>=0.7.5 <0.8.0", "from": "shelljs@>=0.7.5 <0.8.0",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
"dev": true "dev": true
}, },
"sigmund": { "sigmund": {

View File

@ -94,9 +94,14 @@
"electron": "1.6.6", "electron": "1.6.6",
"electron-builder": "19.9.1", "electron-builder": "19.9.1",
"electron-mocha": "3.3.0", "electron-mocha": "3.3.0",
"eslint": "3.18.0", "eslint": "3.19.0",
"eslint-plugin-jsdoc": "^3.1.1", "eslint-config-standard": "10.2.1",
"eslint-plugin-import": "2.7.0",
"eslint-plugin-jsdoc": "3.1.1",
"eslint-plugin-lodash": "2.3.6", "eslint-plugin-lodash": "2.3.6",
"eslint-plugin-node": "5.1.1",
"eslint-plugin-promise": "3.5.0",
"eslint-plugin-standard": "3.0.1",
"file-exists": "1.0.0", "file-exists": "1.0.0",
"html-angular-validate": "0.1.9", "html-angular-validate": "0.1.9",
"mochainon": "1.0.0", "mochainon": "1.0.0",

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
// This script generates Dockerfiles based on a template containing all // This script generates Dockerfiles based on a template containing all
// the necessary dependencies/ to run and build Etcher in multiple platforms. // the necessary dependencies/ to run and build Etcher in multiple platforms.
const _ = require('lodash'); const _ = require('lodash')
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
const currentDirectory = __dirname; const currentDirectory = __dirname
const template = fs.readFileSync(path.join(currentDirectory, 'Dockerfile.template'), { const template = fs.readFileSync(path.join(currentDirectory, 'Dockerfile.template'), {
encoding: 'utf8' encoding: 'utf8'
}); })
_.each([ _.each([
{ {
@ -38,7 +38,7 @@ _.each([
image: 'ubuntu:12.04' image: 'ubuntu:12.04'
} }
], (options) => { ], (options) => {
const result = _.template(template)(options); const result = _.template(template)(options)
const filename = path.join(currentDirectory, `Dockerfile-${options.architecture}`); const filename = path.join(currentDirectory, `Dockerfile-${options.architecture}`)
fs.writeFileSync(filename, result); fs.writeFileSync(filename, result)
}); })

View File

@ -13,18 +13,18 @@
* See: https://github.com/npm/npm/issues/2679 * See: https://github.com/npm/npm/issues/2679
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
const packageJSON = require('../package.json'); const packageJSON = require('../package.json')
const NPM_SHRINKWRAP_FILE_PATH = path.join(__dirname, '..', 'npm-shrinkwrap.json'); const NPM_SHRINKWRAP_FILE_PATH = path.join(__dirname, '..', 'npm-shrinkwrap.json')
const shrinkwrapFile = require(NPM_SHRINKWRAP_FILE_PATH); const shrinkwrapFile = require(NPM_SHRINKWRAP_FILE_PATH)
const platformSpecificDependencies = packageJSON.platformSpecificDependencies; const platformSpecificDependencies = packageJSON.platformSpecificDependencies
const JSON_INDENTATION_SPACES = 2; const JSON_INDENTATION_SPACES = 2
console.log('Removing:', platformSpecificDependencies.join(', ')); console.log('Removing:', platformSpecificDependencies.join(', '))
/** /**
* @summary Get a shrinkwrap dependency object * @summary Get a shrinkwrap dependency object
@ -46,9 +46,9 @@ console.log('Removing:', platformSpecificDependencies.join(', '));
*/ */
const getShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => { const getShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => {
return _.reduce(shrinkwrapPath, (accumulator, dependency) => { return _.reduce(shrinkwrapPath, (accumulator, dependency) => {
return _.get(accumulator, [ 'dependencies', dependency ], {}); return _.get(accumulator, [ 'dependencies', dependency ], {})
}, shrinkwrap); }, shrinkwrap)
}; }
/** /**
* @summary Get a cleaned shrinkwrap dependency object * @summary Get a cleaned shrinkwrap dependency object
@ -74,10 +74,10 @@ const getShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => {
* console.log(object.version); * console.log(object.version);
*/ */
const getPrettyShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => { const getPrettyShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => {
const object = getShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath); const object = getShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath)
if (_.isEmpty(object)) { if (_.isEmpty(object)) {
return null; return null
} }
return { return {
@ -86,8 +86,8 @@ const getPrettyShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => {
version: object.version, version: object.version,
development: Boolean(object.dev), development: Boolean(object.dev),
optional: Boolean(object.optional) optional: Boolean(object.optional)
}; }
}; }
/** /**
* @summary Get the manifest (package.json) of a shrinkwrap dependency * @summary Get the manifest (package.json) of a shrinkwrap dependency
@ -104,26 +104,26 @@ const getPrettyShrinkwrapDependencyObject = (shrinkwrap, shrinkwrapPath) => {
const getShrinkwrapDependencyManifest = (shrinkwrapPath) => { const getShrinkwrapDependencyManifest = (shrinkwrapPath) => {
const manifestPath = _.chain(shrinkwrapPath) const manifestPath = _.chain(shrinkwrapPath)
.flatMap((dependency) => { .flatMap((dependency) => {
return [ 'node_modules', dependency ]; return [ 'node_modules', dependency ]
}) })
.concat([ 'package.json' ]) .concat([ 'package.json' ])
.reduce((accumulator, file) => { .reduce((accumulator, file) => {
return path.join(accumulator, file); return path.join(accumulator, file)
}, '.') }, '.')
.value(); .value()
try { try {
// For example // For example
// ./node_modules/drivelist/node_modules/lodash/package.json // ./node_modules/drivelist/node_modules/lodash/package.json
return require(`.${path.sep}${manifestPath}`); return require(`.${path.sep}${manifestPath}`)
} catch (error) { } catch (error) {
if (error.code === 'MODULE_NOT_FOUND') { if (error.code === 'MODULE_NOT_FOUND') {
return null; return null
} }
throw error; throw error
} }
}; }
/** /**
* @summary Get the top level dependencies of a shrinkwrap object * @summary Get the top level dependencies of a shrinkwrap object
@ -141,8 +141,8 @@ const getShrinkwrapDependencyManifest = (shrinkwrapPath) => {
* > } * > }
*/ */
const getTopLevelDependenciesForShrinkwrapPath = (shrinkwrapPath) => { const getTopLevelDependenciesForShrinkwrapPath = (shrinkwrapPath) => {
return _.get(getShrinkwrapDependencyManifest(shrinkwrapPath), [ 'dependencies' ], {}); return _.get(getShrinkwrapDependencyManifest(shrinkwrapPath), [ 'dependencies' ], {})
}; }
/** /**
* @summary Get the dependency tree of a shrinkwrap dependency * @summary Get the dependency tree of a shrinkwrap dependency
@ -163,22 +163,22 @@ const getTopLevelDependenciesForShrinkwrapPath = (shrinkwrapPath) => {
* }) * })
*/ */
const getDependencyTree = (shrinkwrap, shrinkwrapPath) => { const getDependencyTree = (shrinkwrap, shrinkwrapPath) => {
const dependencies = getTopLevelDependenciesForShrinkwrapPath(shrinkwrapPath); const dependencies = getTopLevelDependenciesForShrinkwrapPath(shrinkwrapPath)
if (_.isEmpty(dependencies)) { if (_.isEmpty(dependencies)) {
return []; return []
} }
const object = getShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath); const object = getShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath)
const result = _.map(dependencies, (version, name) => { const result = _.map(dependencies, (version, name) => {
const dependencyPath = _.has(object.dependencies, name) ? _.concat(shrinkwrapPath, [ name ]) : [ name ]; const dependencyPath = _.has(object.dependencies, name) ? _.concat(shrinkwrapPath, [ name ]) : [ name ]
return getPrettyShrinkwrapDependencyObject(shrinkwrap, dependencyPath); return getPrettyShrinkwrapDependencyObject(shrinkwrap, dependencyPath)
}); })
return _.concat(result, _.flatMapDeep(result, (dependency) => { return _.concat(result, _.flatMapDeep(result, (dependency) => {
return getDependencyTree(shrinkwrap, dependency.path); return getDependencyTree(shrinkwrap, dependency.path)
})); }))
}; }
/** /**
* @summary Remove certain development optional dependencies from a shrinkwrap file * @summary Remove certain development optional dependencies from a shrinkwrap file
@ -224,16 +224,16 @@ const removeOptionalDevelopmentDependencies = (shrinkwrap, blacklist) => {
}), }),
dependency.dev, dependency.dev,
dependency.optional dependency.optional
]); ])
}) })
.mapValues((dependency) => { .mapValues((dependency) => {
return removeOptionalDevelopmentDependencies(dependency, blacklist); return removeOptionalDevelopmentDependencies(dependency, blacklist)
}) })
.value(); .value()
} }
return shrinkwrap; return shrinkwrap
}; }
/** /**
* @summary Get the dependency tree of a dependency plus the dependency itself * @summary Get the dependency tree of a dependency plus the dependency itself
@ -256,15 +256,15 @@ const removeOptionalDevelopmentDependencies = (shrinkwrap, blacklist) => {
const getTree = (shrinkwrap, shrinkwrapPath) => { const getTree = (shrinkwrap, shrinkwrapPath) => {
return _.compact(_.concat([ return _.compact(_.concat([
getPrettyShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath) getPrettyShrinkwrapDependencyObject(shrinkwrap, shrinkwrapPath)
], getDependencyTree(shrinkwrap, shrinkwrapPath))); ], getDependencyTree(shrinkwrap, shrinkwrapPath)))
}; }
const blacklist = _.reduce(platformSpecificDependencies, (accumulator, dependencyPath) => { const blacklist = _.reduce(platformSpecificDependencies, (accumulator, dependencyPath) => {
return _.concat(accumulator, getTree(shrinkwrapFile, dependencyPath)); return _.concat(accumulator, getTree(shrinkwrapFile, dependencyPath))
}, []); }, [])
const filteredShrinkwrap = removeOptionalDevelopmentDependencies(shrinkwrapFile, blacklist); const filteredShrinkwrap = removeOptionalDevelopmentDependencies(shrinkwrapFile, blacklist)
const result = JSON.stringify(filteredShrinkwrap, null, JSON_INDENTATION_SPACES); const result = JSON.stringify(filteredShrinkwrap, null, JSON_INDENTATION_SPACES)
fs.writeFileSync(NPM_SHRINKWRAP_FILE_PATH, `${result}\n`); fs.writeFileSync(NPM_SHRINKWRAP_FILE_PATH, `${result}\n`)
console.log('Done'); console.log('Done')

View File

@ -8,17 +8,17 @@
* node scripts/html-lint.js * node scripts/html-lint.js
*/ */
'use strict'; 'use strict'
const chalk = require('chalk'); const chalk = require('chalk')
const path = require('path'); const path = require('path')
const _ = require('lodash'); const _ = require('lodash')
const angularValidate = require('html-angular-validate'); const angularValidate = require('html-angular-validate')
const EXIT_CODES = require('../lib/shared/exit-codes'); const EXIT_CODES = require('../lib/shared/exit-codes')
const PROJECT_ROOT = path.join(__dirname, '..'); const PROJECT_ROOT = path.join(__dirname, '..')
const FILENAME = path.relative(PROJECT_ROOT, __filename); const FILENAME = path.relative(PROJECT_ROOT, __filename)
console.log('Scanning...'); console.log('Scanning...')
angularValidate.validate( angularValidate.validate(
[ [
@ -48,36 +48,36 @@ angularValidate.validate(
).then((result) => { ).then((result) => {
_.each(result.failed, (failure) => { _.each(result.failed, (failure) => {
// The module has a typo in the "numbers" property // The module has a typo in the "numbers" property
console.error(chalk.red(`${failure.numerrs} errors at ${path.relative(PROJECT_ROOT, failure.filepath)}`)); console.error(chalk.red(`${failure.numerrs} errors at ${path.relative(PROJECT_ROOT, failure.filepath)}`))
_.each(failure.errors, (error) => { _.each(failure.errors, (error) => {
const errorPosition = `[${error.line}:${error.col}]`; const errorPosition = `[${error.line}:${error.col}]`
console.error(` ${chalk.yellow(errorPosition)} ${error.msg}`); console.error(` ${chalk.yellow(errorPosition)} ${error.msg}`)
if (/^Attribute (.*) not allowed on/.test(error.msg)) { if (/^Attribute (.*) not allowed on/.test(error.msg)) {
console.error(chalk.dim(` If this is a valid directive attribute, add it to the whitelist at ${FILENAME}`)); console.error(chalk.dim(` If this is a valid directive attribute, add it to the whitelist at ${FILENAME}`))
} }
}); })
console.error(''); console.error('')
}); })
if (result.filessucceeded === result.fileschecked) { if (result.filessucceeded === result.fileschecked) {
console.log(chalk.green('Passed')); console.log(chalk.green('Passed'))
} else { } else {
console.error(chalk.red(`Total: ${result.filessucceeded}/${result.fileschecked}`)); console.error(chalk.red(`Total: ${result.filessucceeded}/${result.fileschecked}`))
} }
if (!result.allpassed) { if (!result.allpassed) {
const EXIT_TIMEOUT_MS = 500; const EXIT_TIMEOUT_MS = 500
// Add a small timeout, otherwise the scripts exits // Add a small timeout, otherwise the scripts exits
// before every string was printed on the screen. // before every string was printed on the screen.
setTimeout(() => { setTimeout(() => {
process.exit(EXIT_CODES.GENERAL_ERROR); process.exit(EXIT_CODES.GENERAL_ERROR)
}, EXIT_TIMEOUT_MS); }, EXIT_TIMEOUT_MS)
} }
}, (error) => { }, (error) => {
console.error(error); console.error(error)
process.exit(EXIT_CODES.GENERAL_ERROR); process.exit(EXIT_CODES.GENERAL_ERROR)
}); })

View File

@ -14,29 +14,29 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const cli = require('../../lib/child-writer/cli'); const cli = require('../../lib/child-writer/cli')
describe('ChildWriter CLI', function () { describe('ChildWriter CLI', function () {
describe('.getBooleanArgumentForm()', function () { describe('.getBooleanArgumentForm()', function () {
it('should prepend --no if the value is false and option is long', function () { it('should prepend --no if the value is false and option is long', function () {
m.chai.expect(cli.getBooleanArgumentForm('foo', false)).to.equal('--no-foo'); m.chai.expect(cli.getBooleanArgumentForm('foo', false)).to.equal('--no-foo')
}); })
it('should prepend -- if the value is true and option is long', function () { it('should prepend -- if the value is true and option is long', function () {
m.chai.expect(cli.getBooleanArgumentForm('foo', true)).to.equal('--foo'); m.chai.expect(cli.getBooleanArgumentForm('foo', true)).to.equal('--foo')
}); })
it('should prepend --no if the value is false and option is short', function () { it('should prepend --no if the value is false and option is short', function () {
m.chai.expect(cli.getBooleanArgumentForm('x', false)).to.equal('--no-x'); m.chai.expect(cli.getBooleanArgumentForm('x', false)).to.equal('--no-x')
}); })
it('should prepend - if the value is true and option is short', function () { it('should prepend - if the value is true and option is short', function () {
m.chai.expect(cli.getBooleanArgumentForm('x', true)).to.equal('-x'); m.chai.expect(cli.getBooleanArgumentForm('x', true)).to.equal('-x')
}); })
}); })
describe('.getArguments()', function () { describe('.getArguments()', function () {
it('should return a list of arguments given validate = false, unmount = false', function () { it('should return a list of arguments given validate = false, unmount = false', function () {
@ -53,8 +53,8 @@ describe('ChildWriter CLI', function () {
'/dev/disk2', '/dev/disk2',
'--no-unmount', '--no-unmount',
'--no-check' '--no-check'
]); ])
}); })
it('should return a list of arguments given validate = false, unmount = true', function () { it('should return a list of arguments given validate = false, unmount = true', function () {
m.chai.expect(cli.getArguments({ m.chai.expect(cli.getArguments({
@ -70,8 +70,8 @@ describe('ChildWriter CLI', function () {
'/dev/disk2', '/dev/disk2',
'--unmount', '--unmount',
'--no-check' '--no-check'
]); ])
}); })
it('should return a list of arguments given validate = true, unmount = false', function () { it('should return a list of arguments given validate = true, unmount = false', function () {
m.chai.expect(cli.getArguments({ m.chai.expect(cli.getArguments({
@ -87,8 +87,8 @@ describe('ChildWriter CLI', function () {
'/dev/disk2', '/dev/disk2',
'--no-unmount', '--no-unmount',
'--check' '--check'
]); ])
}); })
it('should return a list of arguments given validate = true, unmount = true', function () { it('should return a list of arguments given validate = true, unmount = true', function () {
m.chai.expect(cli.getArguments({ m.chai.expect(cli.getArguments({
@ -104,7 +104,7 @@ describe('ChildWriter CLI', function () {
'/dev/disk2', '/dev/disk2',
'--unmount', '--unmount',
'--check' '--check'
]); ])
}); })
}); })
}); })

View File

@ -14,64 +14,64 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const utils = require('../../lib/child-writer/utils'); const utils = require('../../lib/child-writer/utils')
describe('ChildWriter Utils', function () { describe('ChildWriter Utils', function () {
describe('.splitObjectLines()', function () { describe('.splitObjectLines()', function () {
it('should split multiple object lines', function () { it('should split multiple object lines', function () {
const input = '{"id":"foo"}\n{"id":"bar"}\n{"id":"baz"}'; const input = '{"id":"foo"}\n{"id":"bar"}\n{"id":"baz"}'
m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([ m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([
'{"id":"foo"}', '{"id":"foo"}',
'{"id":"bar"}', '{"id":"bar"}',
'{"id":"baz"}' '{"id":"baz"}'
]); ])
}); })
it('should ignore spaces in between', function () { it('should ignore spaces in between', function () {
const input = '{"id":"foo"} \n {"id":"bar"}\n {"id":"baz"}'; const input = '{"id":"foo"} \n {"id":"bar"}\n {"id":"baz"}'
m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([ m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([
'{"id":"foo"}', '{"id":"foo"}',
'{"id":"bar"}', '{"id":"bar"}',
'{"id":"baz"}' '{"id":"baz"}'
]); ])
}); })
it('should ignore multiple new lines', function () { it('should ignore multiple new lines', function () {
const input = '{"id":"foo"}\n\n\n\n{"id":"bar"}\n\n{"id":"baz"}'; const input = '{"id":"foo"}\n\n\n\n{"id":"bar"}\n\n{"id":"baz"}'
m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([ m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([
'{"id":"foo"}', '{"id":"foo"}',
'{"id":"bar"}', '{"id":"bar"}',
'{"id":"baz"}' '{"id":"baz"}'
]); ])
}); })
it('should ignore new lines inside properties', function () { it('should ignore new lines inside properties', function () {
const input = '{"id":"foo\nbar"}\n{"id":"\nhello\n"}'; const input = '{"id":"foo\nbar"}\n{"id":"\nhello\n"}'
m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([ m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([
'{"id":"foo\nbar"}', '{"id":"foo\nbar"}',
'{"id":"\nhello\n"}' '{"id":"\nhello\n"}'
]); ])
}); })
it('should handle carriage returns', function () { it('should handle carriage returns', function () {
const input = '{"id":"foo"}\r\n{"id":"bar"}\r\n{"id":"baz"}'; const input = '{"id":"foo"}\r\n{"id":"bar"}\r\n{"id":"baz"}'
m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([ m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([
'{"id":"foo"}', '{"id":"foo"}',
'{"id":"bar"}', '{"id":"bar"}',
'{"id":"baz"}' '{"id":"baz"}'
]); ])
}); })
it('should ignore multiple carriage returns', function () { it('should ignore multiple carriage returns', function () {
const input = '{"id":"foo"}\r\n\r\n{"id":"bar"}\r\n\r\n\r\n{"id":"baz"}'; const input = '{"id":"foo"}\r\n\r\n{"id":"bar"}\r\n\r\n\r\n{"id":"baz"}'
m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([ m.chai.expect(utils.splitObjectLines(input)).to.deep.equal([
'{"id":"foo"}', '{"id":"foo"}',
'{"id":"bar"}', '{"id":"bar"}',
'{"id":"baz"}' '{"id":"baz"}'
]); ])
}); })
}); })
}); })

View File

@ -14,26 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const _ = require('lodash'); const _ = require('lodash')
const m = require('mochainon'); const m = require('mochainon')
const angular = require('angular'); const angular = require('angular')
require('angular-mocks'); require('angular-mocks')
describe('Browser: DriveSelector', function () { describe('Browser: DriveSelector', function () {
beforeEach(angular.mock.module( beforeEach(angular.mock.module(
require('../../../lib/gui/components/drive-selector/drive-selector') require('../../../lib/gui/components/drive-selector/drive-selector')
)); ))
describe('DriveSelectorController', function () { describe('DriveSelectorController', function () {
let $controller; let $controller
let $rootScope; let $rootScope
let $q; let $q
let $uibModalInstance; let $uibModalInstance
let WarningModalService; let WarningModalService
let controller; let controller
beforeEach(angular.mock.inject(function ( beforeEach(angular.mock.inject(function (
_$controller_, _$controller_,
@ -41,12 +41,12 @@ describe('Browser: DriveSelector', function () {
_$q_, _$q_,
_WarningModalService_ _WarningModalService_
) { ) {
$controller = _$controller_; $controller = _$controller_
$rootScope = _$rootScope_; $rootScope = _$rootScope_
$q = _$q_; $q = _$q_
$uibModalInstance = {}; $uibModalInstance = {}
WarningModalService = _WarningModalService_; WarningModalService = _WarningModalService_
})); }))
beforeEach(() => { beforeEach(() => {
controller = $controller('DriveSelectorController', { controller = $controller('DriveSelectorController', {
@ -54,49 +54,49 @@ describe('Browser: DriveSelector', function () {
$q, $q,
$uibModalInstance, $uibModalInstance,
WarningModalService WarningModalService
}); })
}); })
describe('.memoizeImmutableListReference()', function () { describe('.memoizeImmutableListReference()', function () {
it('constant true should return memoized true', function () { it('constant true should return memoized true', function () {
const memoizedConstTrue = controller.memoizeImmutableListReference(_.constant(true)); const memoizedConstTrue = controller.memoizeImmutableListReference(_.constant(true))
m.chai.expect(memoizedConstTrue()).to.be.true; m.chai.expect(memoizedConstTrue()).to.be.true
}); })
it('should reflect state changes', function () { it('should reflect state changes', function () {
let stateA = false; let stateA = false
const memoizedStateA = controller.memoizeImmutableListReference(() => { const memoizedStateA = controller.memoizeImmutableListReference(() => {
return stateA; return stateA
}); })
m.chai.expect(memoizedStateA()).to.be.false; m.chai.expect(memoizedStateA()).to.be.false
stateA = true; stateA = true
m.chai.expect(memoizedStateA()).to.be.true; m.chai.expect(memoizedStateA()).to.be.true
}); })
it('should reflect different arguments', function () { it('should reflect different arguments', function () {
const memoizedParameter = controller.memoizeImmutableListReference(_.identity); const memoizedParameter = controller.memoizeImmutableListReference(_.identity)
m.chai.expect(memoizedParameter(false)).to.be.false; m.chai.expect(memoizedParameter(false)).to.be.false
m.chai.expect(memoizedParameter(true)).to.be.true; m.chai.expect(memoizedParameter(true)).to.be.true
}); })
it('should handle equal angular objects with different hashes', function () { it('should handle equal angular objects with different hashes', function () {
const memoizedParameter = controller.memoizeImmutableListReference(_.identity); const memoizedParameter = controller.memoizeImmutableListReference(_.identity)
const angularObjectA = { const angularObjectA = {
$$hashKey: 1, $$hashKey: 1,
keyA: true keyA: true
}; }
const angularObjectB = { const angularObjectB = {
$$hashKey: 2, $$hashKey: 2,
keyA: true keyA: true
}; }
m.chai.expect(memoizedParameter(angularObjectA)).to.equal(angularObjectA); m.chai.expect(memoizedParameter(angularObjectA)).to.equal(angularObjectA)
m.chai.expect(memoizedParameter(angularObjectB)).to.equal(angularObjectA); m.chai.expect(memoizedParameter(angularObjectB)).to.equal(angularObjectA)
}); })
}); })
}); })
}); })

View File

@ -14,86 +14,86 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const _ = require('lodash'); const _ = require('lodash')
const fs = require('fs'); const fs = require('fs')
const path = require('path'); const path = require('path')
const angular = require('angular'); const angular = require('angular')
require('angular-mocks'); require('angular-mocks')
describe('Browser: SVGIcon', function () { describe('Browser: SVGIcon', function () {
beforeEach(angular.mock.module( beforeEach(angular.mock.module(
require('../../../lib/gui/components/svg-icon') require('../../../lib/gui/components/svg-icon')
)); ))
describe('svgIcon', function () { describe('svgIcon', function () {
let $compile; let $compile
let $rootScope; let $rootScope
beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_) { beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_; $compile = _$compile_
$rootScope = _$rootScope_; $rootScope = _$rootScope_
})); }))
it('should inline the svg contents in the element', function () { it('should inline the svg contents in the element', function () {
const icon = '../../../lib/gui/assets/etcher.svg'; const icon = '../../../lib/gui/assets/etcher.svg'
let iconContents = _.split(fs.readFileSync(path.join(__dirname, '../../../lib/gui/assets/etcher.svg'), { let iconContents = _.split(fs.readFileSync(path.join(__dirname, '../../../lib/gui/assets/etcher.svg'), {
encoding: 'utf8' encoding: 'utf8'
}), /\r?\n/); }), /\r?\n/)
// Injecting XML as HTML causes the XML header to be commented out. // Injecting XML as HTML causes the XML header to be commented out.
// Modify here to ease assertions later on. // Modify here to ease assertions later on.
iconContents[0] = `<!--${iconContents[0].slice(1, iconContents[0].length - 1)}-->`; iconContents[0] = `<!--${iconContents[0].slice(1, iconContents[0].length - 1)}-->`
iconContents = iconContents.join('\n'); iconContents = iconContents.join('\n')
const element = $compile(`<svg-icon path="'${icon}'">Resin.io</svg-icon>`)($rootScope); const element = $compile(`<svg-icon path="'${icon}'">Resin.io</svg-icon>`)($rootScope)
$rootScope.$digest(); $rootScope.$digest()
// We parse the SVGs to get rid of discrepancies caused by string differences // We parse the SVGs to get rid of discrepancies caused by string differences
// in the outputs; the XML trees are still equal, as proven here. // in the outputs; the XML trees are still equal, as proven here.
const originalSVGParser = new DOMParser(); const originalSVGParser = new DOMParser()
const originalDoc = originalSVGParser.parseFromString(iconContents, 'image/svg+xml'); const originalDoc = originalSVGParser.parseFromString(iconContents, 'image/svg+xml')
const compiledSVGParser = new DOMParser(); const compiledSVGParser = new DOMParser()
const compiledContents = decodeURIComponent(element.children()[0].children[0].src.substr(19)); const compiledContents = decodeURIComponent(element.children()[0].children[0].src.substr(19))
const compiledDoc = compiledSVGParser.parseFromString(compiledContents, 'image/svg+xml'); const compiledDoc = compiledSVGParser.parseFromString(compiledContents, 'image/svg+xml')
m.chai.expect(compiledDoc.outerHTML).to.equal(originalDoc.outerHTML); m.chai.expect(compiledDoc.outerHTML).to.equal(originalDoc.outerHTML)
}); })
it('should accept an SVG in the path attribute', function () { it('should accept an SVG in the path attribute', function () {
const iconContents = '<svg><rect x="10" y="10" height="100" width="100" style="stroke:red;fill:blue;"/></svg>'; const iconContents = '<svg><rect x="10" y="10" height="100" width="100" style="stroke:red;fill:blue;"/></svg>'
const img = `<img src="data:image/svg+xml,${encodeURIComponent(iconContents)}">`; const img = `<img src="data:image/svg+xml,${encodeURIComponent(iconContents)}">`
$rootScope.iconContents = iconContents; $rootScope.iconContents = iconContents
const element = $compile('<svg-icon path="iconContents">Resin.io</svg-icon>')($rootScope); const element = $compile('<svg-icon path="iconContents">Resin.io</svg-icon>')($rootScope)
$rootScope.$digest(); $rootScope.$digest()
m.chai.expect(element.children().html()).to.equal(img); m.chai.expect(element.children().html()).to.equal(img)
}); })
it('should default the size to 40x40 pixels', function () { it('should default the size to 40x40 pixels', function () {
const icon = '../../../lib/gui/assets/etcher.svg'; const icon = '../../../lib/gui/assets/etcher.svg'
const element = $compile(`<svg-icon path="'${icon}'">Resin.io</svg-icon>`)($rootScope); const element = $compile(`<svg-icon path="'${icon}'">Resin.io</svg-icon>`)($rootScope)
$rootScope.$digest(); $rootScope.$digest()
m.chai.expect(element.children().css('width')).to.equal('40px'); m.chai.expect(element.children().css('width')).to.equal('40px')
m.chai.expect(element.children().css('height')).to.equal('40px'); m.chai.expect(element.children().css('height')).to.equal('40px')
}); })
it('should be able to set a custom width', function () { it('should be able to set a custom width', function () {
const icon = '../../../lib/gui/assets/etcher.svg'; const icon = '../../../lib/gui/assets/etcher.svg'
const element = $compile(`<svg-icon path="'${icon}'" width="'20px'">Resin.io</svg-icon>`)($rootScope); const element = $compile(`<svg-icon path="'${icon}'" width="'20px'">Resin.io</svg-icon>`)($rootScope)
$rootScope.$digest(); $rootScope.$digest()
m.chai.expect(element.children().css('width')).to.equal('20px'); m.chai.expect(element.children().css('width')).to.equal('20px')
m.chai.expect(element.children().css('height')).to.equal('40px'); m.chai.expect(element.children().css('height')).to.equal('40px')
}); })
it('should be able to set a custom height', function () { it('should be able to set a custom height', function () {
const icon = '../../../lib/gui/assets/etcher.svg'; const icon = '../../../lib/gui/assets/etcher.svg'
const element = $compile(`<svg-icon path="'${icon}'" height="'20px'">Resin.io</svg-icon>`)($rootScope); const element = $compile(`<svg-icon path="'${icon}'" height="'20px'">Resin.io</svg-icon>`)($rootScope)
$rootScope.$digest(); $rootScope.$digest()
m.chai.expect(element.children().css('width')).to.equal('40px'); m.chai.expect(element.children().css('width')).to.equal('40px')
m.chai.expect(element.children().css('height')).to.equal('20px'); m.chai.expect(element.children().css('height')).to.equal('20px')
}); })
}); })
}); })

View File

@ -14,26 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const _ = require('lodash'); const _ = require('lodash')
const units = require('../../../lib/shared/units'); const units = require('../../../lib/shared/units')
const updateNotifier = require('../../../lib/gui/components/update-notifier'); const updateNotifier = require('../../../lib/gui/components/update-notifier')
describe('Browser: updateNotifier', function () { describe('Browser: updateNotifier', function () {
describe('.UPDATE_NOTIFIER_SLEEP_DAYS', function () { describe('.UPDATE_NOTIFIER_SLEEP_DAYS', function () {
it('should be an integer', function () { it('should be an integer', function () {
m.chai.expect(_.isInteger(updateNotifier.UPDATE_NOTIFIER_SLEEP_DAYS)).to.be.true; m.chai.expect(_.isInteger(updateNotifier.UPDATE_NOTIFIER_SLEEP_DAYS)).to.be.true
}); })
it('should be greater than 0', function () { it('should be greater than 0', function () {
m.chai.expect(updateNotifier.UPDATE_NOTIFIER_SLEEP_DAYS > 0).to.be.true; m.chai.expect(updateNotifier.UPDATE_NOTIFIER_SLEEP_DAYS > 0).to.be.true
}); })
}); })
describe('.shouldCheckForUpdates()', function () { describe('.shouldCheckForUpdates()', function () {
const UPDATE_NOTIFIER_SLEEP_MS = units.daysToMilliseconds(updateNotifier.UPDATE_NOTIFIER_SLEEP_DAYS); const UPDATE_NOTIFIER_SLEEP_MS = units.daysToMilliseconds(updateNotifier.UPDATE_NOTIFIER_SLEEP_DAYS)
_.each([ _.each([
@ -443,8 +443,8 @@ describe('Browser: updateNotifier', function () {
`lastSleptUpdateNotifierVersion=${testCase.options.lastSleptUpdateNotifierVersion}, and`, `lastSleptUpdateNotifierVersion=${testCase.options.lastSleptUpdateNotifierVersion}, and`,
`currentVersion=${testCase.options.currentVersion}` `currentVersion=${testCase.options.currentVersion}`
], ' '), function () { ], ' '), function () {
m.chai.expect(updateNotifier.shouldCheckForUpdates(testCase.options)).to.equal(testCase.expected); m.chai.expect(updateNotifier.shouldCheckForUpdates(testCase.options)).to.equal(testCase.expected)
}); })
}); })
}); })
}); })

View File

@ -14,84 +14,84 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const _ = require('lodash'); const _ = require('lodash')
const Bluebird = require('bluebird'); const Bluebird = require('bluebird')
const store = require('../../../lib/shared/store'); const store = require('../../../lib/shared/store')
const settings = require('../../../lib/gui/models/settings'); const settings = require('../../../lib/gui/models/settings')
const localSettings = require('../../../lib/gui/models/local-settings'); const localSettings = require('../../../lib/gui/models/local-settings')
describe('Browser: settings', function () { describe('Browser: settings', function () {
beforeEach(function () { beforeEach(function () {
return settings.reset(); return settings.reset()
}); })
const DEFAULT_SETTINGS = store.Defaults.get('settings').toJS(); const DEFAULT_SETTINGS = store.Defaults.get('settings').toJS()
it('should be able to set and read values', function () { it('should be able to set and read values', function () {
m.chai.expect(settings.get('foo')).to.be.undefined; m.chai.expect(settings.get('foo')).to.be.undefined
return settings.set('foo', true).then(() => { return settings.set('foo', true).then(() => {
m.chai.expect(settings.get('foo')).to.be.true; m.chai.expect(settings.get('foo')).to.be.true
return settings.set('foo', false); return settings.set('foo', false)
}).then(() => { }).then(() => {
m.chai.expect(settings.get('foo')).to.be.false; m.chai.expect(settings.get('foo')).to.be.false
}); })
}); })
describe('.reset()', function () { describe('.reset()', function () {
it('should reset the settings to their default values', function () { it('should reset the settings to their default values', function () {
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
return settings.set('foo', 1234).then(() => { return settings.set('foo', 1234).then(() => {
m.chai.expect(settings.getAll()).to.not.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.not.deep.equal(DEFAULT_SETTINGS)
return settings.reset(); return settings.reset()
}).then(() => { }).then(() => {
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
}); })
}); })
it('should reset the local settings to their default values', function () { it('should reset the local settings to their default values', function () {
return settings.set('foo', 1234).then(localSettings.readAll).then((data) => { return settings.set('foo', 1234).then(localSettings.readAll).then((data) => {
m.chai.expect(data).to.not.deep.equal(DEFAULT_SETTINGS); m.chai.expect(data).to.not.deep.equal(DEFAULT_SETTINGS)
return settings.reset(); return settings.reset()
}).then(localSettings.readAll).then((data) => { }).then(localSettings.readAll).then((data) => {
m.chai.expect(data).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(data).to.deep.equal(DEFAULT_SETTINGS)
}); })
}); })
describe('given the local settings are cleared', function () { describe('given the local settings are cleared', function () {
beforeEach(function () { beforeEach(function () {
return localSettings.clear(); return localSettings.clear()
}); })
it('should set the local settings to their default values', function () { it('should set the local settings to their default values', function () {
return settings.reset().then(localSettings.readAll).then((data) => { return settings.reset().then(localSettings.readAll).then((data) => {
m.chai.expect(data).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(data).to.deep.equal(DEFAULT_SETTINGS)
}); })
}); })
}); })
}); })
describe('.assign()', function () { describe('.assign()', function () {
it('should throw if no settings', function (done) { it('should throw if no settings', function (done) {
settings.assign().asCallback((error) => { settings.assign().asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Missing settings'); m.chai.expect(error.message).to.equal('Missing settings')
done(); done()
}); })
}); })
it('should throw if setting an array', function (done) { it('should throw if setting an array', function (done) {
settings.assign({ settings.assign({
foo: 'bar', foo: 'bar',
bar: [ 1, 2, 3 ] bar: [ 1, 2, 3 ]
}).asCallback((error) => { }).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for bar'); m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for bar')
done(); done()
}); })
}); })
it('should not override all settings', function () { it('should not override all settings', function () {
return settings.assign({ return settings.assign({
@ -101,184 +101,184 @@ describe('Browser: settings', function () {
m.chai.expect(settings.getAll()).to.deep.equal(_.assign({}, DEFAULT_SETTINGS, { m.chai.expect(settings.getAll()).to.deep.equal(_.assign({}, DEFAULT_SETTINGS, {
foo: 'bar', foo: 'bar',
bar: 'baz' bar: 'baz'
})); }))
}); })
}); })
it('should not store invalid settings to the local machine', function () { it('should not store invalid settings to the local machine', function () {
return localSettings.readAll().then((data) => { return localSettings.readAll().then((data) => {
m.chai.expect(data.foo).to.be.undefined; m.chai.expect(data.foo).to.be.undefined
return new Bluebird((resolve) => { return new Bluebird((resolve) => {
settings.assign({ settings.assign({
foo: [ 1, 2, 3 ] foo: [ 1, 2, 3 ]
}).asCallback((error) => { }).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for foo'); m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for foo')
return resolve(); return resolve()
}); })
}); })
}).then(localSettings.readAll).then((data) => { }).then(localSettings.readAll).then((data) => {
m.chai.expect(data.foo).to.be.undefined; m.chai.expect(data.foo).to.be.undefined
}); })
}); })
it('should store the settings to the local machine', function () { it('should store the settings to the local machine', function () {
return localSettings.readAll().then((data) => { return localSettings.readAll().then((data) => {
m.chai.expect(data.foo).to.be.undefined; m.chai.expect(data.foo).to.be.undefined
m.chai.expect(data.bar).to.be.undefined; m.chai.expect(data.bar).to.be.undefined
return settings.assign({ return settings.assign({
foo: 'bar', foo: 'bar',
bar: 'baz' bar: 'baz'
}); })
}).then(localSettings.readAll).then((data) => { }).then(localSettings.readAll).then((data) => {
m.chai.expect(data.foo).to.equal('bar'); m.chai.expect(data.foo).to.equal('bar')
m.chai.expect(data.bar).to.equal('baz'); m.chai.expect(data.bar).to.equal('baz')
}); })
}); })
it('should not change the application state if storing to the local machine results in an error', function (done) { it('should not change the application state if storing to the local machine results in an error', function (done) {
settings.set('foo', 'bar').then(() => { settings.set('foo', 'bar').then(() => {
m.chai.expect(settings.get('foo')).to.equal('bar'); m.chai.expect(settings.get('foo')).to.equal('bar')
const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll'); const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll')
localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error'))); localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error')))
settings.assign({ settings.assign({
foo: 'baz' foo: 'baz'
}).asCallback((error) => { }).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('localSettings error'); m.chai.expect(error.message).to.equal('localSettings error')
localSettingsWriteAllStub.restore(); localSettingsWriteAllStub.restore()
m.chai.expect(settings.get('foo')).to.equal('bar'); m.chai.expect(settings.get('foo')).to.equal('bar')
done(); done()
}); })
}).catch(done); }).catch(done)
}); })
}); })
describe('.load()', function () { describe('.load()', function () {
it('should extend the application state with the local settings content', function () { it('should extend the application state with the local settings content', function () {
const object = { const object = {
foo: 'bar' foo: 'bar'
}; }
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
return localSettings.writeAll(object).then(() => { return localSettings.writeAll(object).then(() => {
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
return settings.load(); return settings.load()
}).then(() => { }).then(() => {
m.chai.expect(settings.getAll()).to.deep.equal(_.assign({}, DEFAULT_SETTINGS, object)); m.chai.expect(settings.getAll()).to.deep.equal(_.assign({}, DEFAULT_SETTINGS, object))
}); })
}); })
it('should keep the application state intact if there are no local settings', function () { it('should keep the application state intact if there are no local settings', function () {
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
return localSettings.clear().then(settings.load).then(() => { return localSettings.clear().then(settings.load).then(() => {
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
}); })
}); })
}); })
describe('.set()', function () { describe('.set()', function () {
it('should set an unknown key', function () { it('should set an unknown key', function () {
m.chai.expect(settings.get('foobar')).to.be.undefined; m.chai.expect(settings.get('foobar')).to.be.undefined
return settings.set('foobar', true).then(() => { return settings.set('foobar', true).then(() => {
m.chai.expect(settings.get('foobar')).to.be.true; m.chai.expect(settings.get('foobar')).to.be.true
}); })
}); })
it('should reject if no key', function (done) { it('should reject if no key', function (done) {
settings.set(null, true).asCallback((error) => { settings.set(null, true).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Missing setting key'); m.chai.expect(error.message).to.equal('Missing setting key')
done(); done()
}); })
}); })
it('should throw if key is not a string', function (done) { it('should throw if key is not a string', function (done) {
settings.set(1234, true).asCallback((error) => { settings.set(1234, true).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting key: 1234'); m.chai.expect(error.message).to.equal('Invalid setting key: 1234')
done(); done()
}); })
}); })
it('should throw if setting an object', function (done) { it('should throw if setting an object', function (done) {
settings.set('foo', { settings.set('foo', {
setting: 1 setting: 1
}).asCallback((error) => { }).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting value: [object Object] for foo'); m.chai.expect(error.message).to.equal('Invalid setting value: [object Object] for foo')
done(); done()
}); })
}); })
it('should throw if setting an array', function (done) { it('should throw if setting an array', function (done) {
settings.set('foo', [ 1, 2, 3 ]).asCallback((error) => { settings.set('foo', [ 1, 2, 3 ]).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for foo'); m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for foo')
done(); done()
}); })
}); })
it('should set the key to undefined if no value', function () { it('should set the key to undefined if no value', function () {
return settings.set('foo', 'bar').then(() => { return settings.set('foo', 'bar').then(() => {
m.chai.expect(settings.get('foo')).to.equal('bar'); m.chai.expect(settings.get('foo')).to.equal('bar')
return settings.set('foo'); return settings.set('foo')
}).then(() => { }).then(() => {
m.chai.expect(settings.get('foo')).to.be.undefined; m.chai.expect(settings.get('foo')).to.be.undefined
}); })
}); })
it('should store the setting to the local machine', function () { it('should store the setting to the local machine', function () {
return localSettings.readAll().then((data) => { return localSettings.readAll().then((data) => {
m.chai.expect(data.foo).to.be.undefined; m.chai.expect(data.foo).to.be.undefined
return settings.set('foo', 'bar'); return settings.set('foo', 'bar')
}).then(localSettings.readAll).then((data) => { }).then(localSettings.readAll).then((data) => {
m.chai.expect(data.foo).to.equal('bar'); m.chai.expect(data.foo).to.equal('bar')
}); })
}); })
it('should not store invalid settings to the local machine', function () { it('should not store invalid settings to the local machine', function () {
return localSettings.readAll().then((data) => { return localSettings.readAll().then((data) => {
m.chai.expect(data.foo).to.be.undefined; m.chai.expect(data.foo).to.be.undefined
return new Bluebird((resolve) => { return new Bluebird((resolve) => {
settings.set('foo', [ 1, 2, 3 ]).asCallback((error) => { settings.set('foo', [ 1, 2, 3 ]).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for foo'); m.chai.expect(error.message).to.equal('Invalid setting value: 1,2,3 for foo')
return resolve(); return resolve()
}); })
}); })
}).then(localSettings.readAll).then((data) => { }).then(localSettings.readAll).then((data) => {
m.chai.expect(data.foo).to.be.undefined; m.chai.expect(data.foo).to.be.undefined
}); })
}); })
it('should not change the application state if storing to the local machine results in an error', function (done) { it('should not change the application state if storing to the local machine results in an error', function (done) {
settings.set('foo', 'bar').then(() => { settings.set('foo', 'bar').then(() => {
m.chai.expect(settings.get('foo')).to.equal('bar'); m.chai.expect(settings.get('foo')).to.equal('bar')
const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll'); const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll')
localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error'))); localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error')))
settings.set('foo', 'baz').asCallback((error) => { settings.set('foo', 'baz').asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('localSettings error'); m.chai.expect(error.message).to.equal('localSettings error')
localSettingsWriteAllStub.restore(); localSettingsWriteAllStub.restore()
m.chai.expect(settings.get('foo')).to.equal('bar'); m.chai.expect(settings.get('foo')).to.equal('bar')
done(); done()
}); })
}).catch(done); }).catch(done)
}); })
}); })
describe('.getAll()', function () { describe('.getAll()', function () {
it('should initial return all default values', function () { it('should initial return all default values', function () {
m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS); m.chai.expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS)
}); })
}); })
}); })

View File

@ -14,38 +14,38 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const os = require('os'); const os = require('os')
const drivelist = require('drivelist'); const drivelist = require('drivelist')
const driveScanner = require('../../../lib/gui/modules/drive-scanner'); const driveScanner = require('../../../lib/gui/modules/drive-scanner')
describe('Browser: driveScanner', function () { describe('Browser: driveScanner', function () {
describe('given no available drives', function () { describe('given no available drives', function () {
beforeEach(function () { beforeEach(function () {
this.drivelistStub = m.sinon.stub(drivelist, 'list'); this.drivelistStub = m.sinon.stub(drivelist, 'list')
this.drivelistStub.yields(null, []); this.drivelistStub.yields(null, [])
}); })
afterEach(function () { afterEach(function () {
this.drivelistStub.restore(); this.drivelistStub.restore()
}); })
it('should emit an empty array', function (done) { it('should emit an empty array', function (done) {
driveScanner.once('drives', function (drives) { driveScanner.once('drives', function (drives) {
m.chai.expect(drives).to.deep.equal([]); m.chai.expect(drives).to.deep.equal([])
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
describe('given only system available drives', function () { describe('given only system available drives', function () {
beforeEach(function () { beforeEach(function () {
this.drivelistStub = m.sinon.stub(drivelist, 'list'); this.drivelistStub = m.sinon.stub(drivelist, 'list')
this.drivelistStub.yields(null, [ { this.drivelistStub.yields(null, [ {
device: '/dev/sda', device: '/dev/sda',
description: 'WDC WD10JPVX-75J', description: 'WDC WD10JPVX-75J',
@ -56,37 +56,37 @@ describe('Browser: driveScanner', function () {
} }
], ],
system: true system: true
} ]); } ])
}); })
afterEach(function () { afterEach(function () {
this.drivelistStub.restore(); this.drivelistStub.restore()
}); })
it('should emit an empty array', function (done) { it('should emit an empty array', function (done) {
driveScanner.once('drives', function (drives) { driveScanner.once('drives', function (drives) {
m.chai.expect(drives).to.deep.equal([]); m.chai.expect(drives).to.deep.equal([])
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
describe('given linux', function () { describe('given linux', function () {
beforeEach(function () { beforeEach(function () {
this.osPlatformStub = m.sinon.stub(os, 'platform'); this.osPlatformStub = m.sinon.stub(os, 'platform')
this.osPlatformStub.returns('linux'); this.osPlatformStub.returns('linux')
}); })
afterEach(function () { afterEach(function () {
this.osPlatformStub.restore(); this.osPlatformStub.restore()
}); })
describe('given available drives', function () { describe('given available drives', function () {
beforeEach(function () { beforeEach(function () {
this.drivelistStub = m.sinon.stub(drivelist, 'list'); this.drivelistStub = m.sinon.stub(drivelist, 'list')
this.drivelistStub.yields(null, [ this.drivelistStub.yields(null, [
{ {
device: '/dev/sda', device: '/dev/sda',
@ -124,12 +124,12 @@ describe('Browser: driveScanner', function () {
], ],
system: false system: false
} }
]); ])
}); })
afterEach(function () { afterEach(function () {
this.drivelistStub.restore(); this.drivelistStub.restore()
}); })
it('should emit the non removable drives', function (done) { it('should emit the non removable drives', function (done) {
driveScanner.once('drives', function (drives) { driveScanner.once('drives', function (drives) {
@ -158,30 +158,30 @@ describe('Browser: driveScanner', function () {
], ],
system: false system: false
} }
]); ])
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
}); })
describe('given windows', function () { describe('given windows', function () {
beforeEach(function () { beforeEach(function () {
this.osPlatformStub = m.sinon.stub(os, 'platform'); this.osPlatformStub = m.sinon.stub(os, 'platform')
this.osPlatformStub.returns('win32'); this.osPlatformStub.returns('win32')
}); })
afterEach(function () { afterEach(function () {
this.osPlatformStub.restore(); this.osPlatformStub.restore()
}); })
describe('given available drives', function () { describe('given available drives', function () {
beforeEach(function () { beforeEach(function () {
this.drivelistStub = m.sinon.stub(drivelist, 'list'); this.drivelistStub = m.sinon.stub(drivelist, 'list')
this.drivelistStub.yields(null, [ this.drivelistStub.yields(null, [
{ {
device: '\\\\.\\PHYSICALDRIVE1', device: '\\\\.\\PHYSICALDRIVE1',
@ -215,12 +215,12 @@ describe('Browser: driveScanner', function () {
], ],
system: false system: false
} }
]); ])
}); })
afterEach(function () { afterEach(function () {
this.drivelistStub.restore(); this.drivelistStub.restore()
}); })
it('should emit the non removable drives', function (done) { it('should emit the non removable drives', function (done) {
driveScanner.once('drives', function (drives) { driveScanner.once('drives', function (drives) {
@ -245,19 +245,19 @@ describe('Browser: driveScanner', function () {
], ],
system: false system: false
} }
]); ])
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
describe('given a drive with a single drive letters', function () { describe('given a drive with a single drive letters', function () {
beforeEach(function () { beforeEach(function () {
this.drivelistStub = m.sinon.stub(drivelist, 'list'); this.drivelistStub = m.sinon.stub(drivelist, 'list')
this.drivelistStub.yields(null, [ this.drivelistStub.yields(null, [
{ {
device: '\\\\.\\PHYSICALDRIVE3', device: '\\\\.\\PHYSICALDRIVE3',
@ -271,28 +271,28 @@ describe('Browser: driveScanner', function () {
], ],
system: false system: false
} }
]); ])
}); })
afterEach(function () { afterEach(function () {
this.drivelistStub.restore(); this.drivelistStub.restore()
}); })
it('should use the drive letter as the name', function (done) { it('should use the drive letter as the name', function (done) {
driveScanner.once('drives', function (drives) { driveScanner.once('drives', function (drives) {
m.chai.expect(drives).to.have.length(1); m.chai.expect(drives).to.have.length(1)
m.chai.expect(drives[0].displayName).to.equal('F:'); m.chai.expect(drives[0].displayName).to.equal('F:')
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
describe('given a drive with multiple drive letters', function () { describe('given a drive with multiple drive letters', function () {
beforeEach(function () { beforeEach(function () {
this.drivesListStub = m.sinon.stub(drivelist, 'list'); this.drivesListStub = m.sinon.stub(drivelist, 'list')
this.drivesListStub.yields(null, [ this.drivesListStub.yields(null, [
{ {
device: '\\\\.\\PHYSICALDRIVE3', device: '\\\\.\\PHYSICALDRIVE3',
@ -312,45 +312,45 @@ describe('Browser: driveScanner', function () {
], ],
system: false system: false
} }
]); ])
}); })
afterEach(function () { afterEach(function () {
this.drivesListStub.restore(); this.drivesListStub.restore()
}); })
it('should join all the mountpoints in `name`', function (done) { it('should join all the mountpoints in `name`', function (done) {
driveScanner.once('drives', function (drives) { driveScanner.once('drives', function (drives) {
m.chai.expect(drives).to.have.length(1); m.chai.expect(drives).to.have.length(1)
m.chai.expect(drives[0].displayName).to.equal('F:, G:, H:'); m.chai.expect(drives[0].displayName).to.equal('F:, G:, H:')
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
}); })
describe('given an error when listing the drives', function () { describe('given an error when listing the drives', function () {
beforeEach(function () { beforeEach(function () {
this.drivesListStub = m.sinon.stub(drivelist, 'list'); this.drivesListStub = m.sinon.stub(drivelist, 'list')
this.drivesListStub.yields(new Error('scan error')); this.drivesListStub.yields(new Error('scan error'))
}); })
afterEach(function () { afterEach(function () {
this.drivesListStub.restore(); this.drivesListStub.restore()
}); })
it('should emit the error', function (done) { it('should emit the error', function (done) {
driveScanner.on('error', function (error) { driveScanner.on('error', function (error) {
m.chai.expect(error).to.be.an.instanceof(Error); m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('scan error'); m.chai.expect(error.message).to.equal('scan error')
driveScanner.stop(); driveScanner.stop()
done(); done()
}); })
driveScanner.start(); driveScanner.start()
}); })
}); })
}); })

View File

@ -14,123 +14,123 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const angular = require('angular'); const angular = require('angular')
const flashState = require('../../../lib/shared/models/flash-state'); const flashState = require('../../../lib/shared/models/flash-state')
require('angular-mocks'); require('angular-mocks')
describe('Browser: ImageWriter', function () { describe('Browser: ImageWriter', function () {
beforeEach(angular.mock.module( beforeEach(angular.mock.module(
require('../../../lib/gui/modules/image-writer') require('../../../lib/gui/modules/image-writer')
)); ))
describe('ImageWriterService', function () { describe('ImageWriterService', function () {
let $q; let $q
let $rootScope; let $rootScope
let ImageWriterService; let ImageWriterService
beforeEach(angular.mock.inject(function (_$q_, _$rootScope_, _ImageWriterService_) { beforeEach(angular.mock.inject(function (_$q_, _$rootScope_, _ImageWriterService_) {
$q = _$q_; $q = _$q_
$rootScope = _$rootScope_; $rootScope = _$rootScope_
ImageWriterService = _ImageWriterService_; ImageWriterService = _ImageWriterService_
})); }))
describe('.flash()', function () { describe('.flash()', function () {
describe('given a successful write', function () { describe('given a successful write', function () {
beforeEach(function () { beforeEach(function () {
this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite'); this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite')
this.performWriteStub.returns($q.resolve({ this.performWriteStub.returns($q.resolve({
cancelled: false, cancelled: false,
sourceChecksum: '1234' sourceChecksum: '1234'
})); }))
}); })
afterEach(function () { afterEach(function () {
this.performWriteStub.restore(); this.performWriteStub.restore()
}); })
it('should set flashing to false when done', function () { it('should set flashing to false when done', function () {
flashState.unsetFlashingFlag({ flashState.unsetFlashingFlag({
cancelled: false, cancelled: false,
sourceChecksum: '1234' sourceChecksum: '1234'
}); })
ImageWriterService.flash('foo.img', '/dev/disk2'); ImageWriterService.flash('foo.img', '/dev/disk2')
$rootScope.$apply(); $rootScope.$apply()
m.chai.expect(flashState.isFlashing()).to.be.false; m.chai.expect(flashState.isFlashing()).to.be.false
}); })
it('should prevent writing more than once', function () { it('should prevent writing more than once', function () {
flashState.unsetFlashingFlag({ flashState.unsetFlashingFlag({
cancelled: false, cancelled: false,
sourceChecksum: '1234' sourceChecksum: '1234'
}); })
ImageWriterService.flash('foo.img', '/dev/disk2'); ImageWriterService.flash('foo.img', '/dev/disk2')
ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop); ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop)
$rootScope.$apply(); $rootScope.$apply()
m.chai.expect(this.performWriteStub).to.have.been.calledOnce; m.chai.expect(this.performWriteStub).to.have.been.calledOnce
}); })
it('should reject the second flash attempt', function () { it('should reject the second flash attempt', function () {
ImageWriterService.flash('foo.img', '/dev/disk2'); ImageWriterService.flash('foo.img', '/dev/disk2')
let rejectError = null; let rejectError = null
ImageWriterService.flash('foo.img', '/dev/disk2').catch(function (error) { ImageWriterService.flash('foo.img', '/dev/disk2').catch(function (error) {
rejectError = error; rejectError = error
}); })
$rootScope.$apply(); $rootScope.$apply()
m.chai.expect(rejectError).to.be.an.instanceof(Error); m.chai.expect(rejectError).to.be.an.instanceof(Error)
m.chai.expect(rejectError.message).to.equal('There is already a flash in progress'); m.chai.expect(rejectError.message).to.equal('There is already a flash in progress')
}); })
}); })
describe('given an unsuccessful write', function () { describe('given an unsuccessful write', function () {
beforeEach(function () { beforeEach(function () {
this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite'); this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite')
this.error = new Error('write error'); this.error = new Error('write error')
this.error.code = 'FOO'; this.error.code = 'FOO'
this.performWriteStub.returns($q.reject(this.error)); this.performWriteStub.returns($q.reject(this.error))
}); })
afterEach(function () { afterEach(function () {
this.performWriteStub.restore(); this.performWriteStub.restore()
}); })
it('should set flashing to false when done', function () { it('should set flashing to false when done', function () {
ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop); ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop)
$rootScope.$apply(); $rootScope.$apply()
m.chai.expect(flashState.isFlashing()).to.be.false; m.chai.expect(flashState.isFlashing()).to.be.false
}); })
it('should set the error code in the flash results', function () { it('should set the error code in the flash results', function () {
ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop); ImageWriterService.flash('foo.img', '/dev/disk2').catch(angular.noop)
$rootScope.$apply(); $rootScope.$apply()
const flashResults = flashState.getFlashResults(); const flashResults = flashState.getFlashResults()
m.chai.expect(flashResults.errorCode).to.equal('FOO'); m.chai.expect(flashResults.errorCode).to.equal('FOO')
}); })
it('should be rejected with the error', function () { it('should be rejected with the error', function () {
flashState.unsetFlashingFlag({ flashState.unsetFlashingFlag({
cancelled: false, cancelled: false,
sourceChecksum: '1234' sourceChecksum: '1234'
}); })
let rejection; let rejection
ImageWriterService.flash('foo.img', '/dev/disk2').catch(function (error) { ImageWriterService.flash('foo.img', '/dev/disk2').catch(function (error) {
rejection = error; rejection = error
}); })
$rootScope.$apply(); $rootScope.$apply()
m.chai.expect(rejection).to.be.an.instanceof(Error); m.chai.expect(rejection).to.be.an.instanceof(Error)
m.chai.expect(rejection.message).to.equal('write error'); m.chai.expect(rejection.message).to.equal('write error')
}); })
}); })
}); })
}); })
}); })

View File

@ -14,36 +14,36 @@
* limitations under the License. * limitations under the License.
*/ */
'use strict'; 'use strict'
const m = require('mochainon'); const m = require('mochainon')
const angular = require('angular'); const angular = require('angular')
require('angular-mocks'); require('angular-mocks')
describe('Browser: OSDropzone', function () { describe('Browser: OSDropzone', function () {
beforeEach(angular.mock.module( beforeEach(angular.mock.module(
require('../../../lib/gui/os/dropzone/dropzone') require('../../../lib/gui/os/dropzone/dropzone')
)); ))
describe('osDropzone', function () { describe('osDropzone', function () {
let $compile; let $compile
let $rootScope; let $rootScope
let $timeout; let $timeout
beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_, _$timeout_) { beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_, _$timeout_) {
$compile = _$compile_; $compile = _$compile_
$rootScope = _$rootScope_; $rootScope = _$rootScope_
$timeout = _$timeout_; $timeout = _$timeout_
})); }))
it('should pass the file back to the callback as $file', function (done) { it('should pass the file back to the callback as $file', function (done) {
$rootScope.onDropZone = function (file) { $rootScope.onDropZone = function (file) {
m.chai.expect(file).to.deep.equal('/foo/bar'); m.chai.expect(file).to.deep.equal('/foo/bar')
done(); done()
}; }
const element = $compile('<div os-dropzone="onDropZone($file)">Drop a file here</div>')($rootScope); const element = $compile('<div os-dropzone="onDropZone($file)">Drop a file here</div>')($rootScope)
$rootScope.$digest(); $rootScope.$digest()
element[0].ondrop({ element[0].ondrop({
preventDefault: angular.noop, preventDefault: angular.noop,
@ -54,20 +54,20 @@ describe('Browser: OSDropzone', function () {
} }
] ]
} }
}); })
$rootScope.$digest(); $rootScope.$digest()
$timeout.flush(); $timeout.flush()
}); })
it('should pass undefined to the callback if not passing $file', function (done) { it('should pass undefined to the callback if not passing $file', function (done) {
$rootScope.onDropZone = function (file) { $rootScope.onDropZone = function (file) {
m.chai.expect(file).to.be.undefined; m.chai.expect(file).to.be.undefined
done(); done()
}; }
const element = $compile('<div os-dropzone="onDropZone()">Drop a file here</div>')($rootScope); const element = $compile('<div os-dropzone="onDropZone()">Drop a file here</div>')($rootScope)
$rootScope.$digest(); $rootScope.$digest()
element[0].ondrop({ element[0].ondrop({
preventDefault: angular.noop, preventDefault: angular.noop,
@ -78,10 +78,10 @@ describe('Browser: OSDropzone', function () {
} }
] ]
} }
}); })
$rootScope.$digest(); $rootScope.$digest()
$timeout.flush(); $timeout.flush()
}); })
}); })
}); })

Some files were not shown because too many files have changed in this diff Show More