mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-19 17:26:34 +00:00
chore: revise ESLint built-in configuration (#1149)
There are a lot of new rules since the last time I revised the ESLint rules documentation. I've updated the main `.eslintrc.yml` to include some newer additions, plus I added another ESLint configuration file inside `tests`, so we can add some stricted rules to the production code while relaxing them for the test suite (due to the fact that Mocha is not very ES6 friendly and Angular tests require a bit of dark magic to setup). This is a summary of the most important changes: - Disallow "magic numbers" These should now be extracted to constants, which forces us to think of a good name for them, and thus make the code more self-documenting (I had to Google up the meaning of some existing magic numbers, so I guess this will be great for readability purposes). - Require consistent `return` statements Some functions relied on JavaScript relaxed casting mechanism to work, which now have explicit return values. This flag also helped me detect some promises that were not being returned, and therefore risked not being caught by the exception handlers in case of errors. - Disallow redefining function arguments Immutability makes functions easier to reason about. - Enforce JavaScript string templates instead of string concatenation We were heavily mixing boths across the codebase. There are some extra rules that I tweaked, however most of codebase changes in this commit are related to the rules mentioned above. Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
parent
c93f528f96
commit
6c8bc117ab
102
.eslintrc.yml
102
.eslintrc.yml
@ -9,9 +9,6 @@ rules:
|
|||||||
|
|
||||||
# Possible Errors
|
# Possible Errors
|
||||||
|
|
||||||
comma-dangle:
|
|
||||||
- error
|
|
||||||
- never
|
|
||||||
no-cond-assign:
|
no-cond-assign:
|
||||||
- error
|
- error
|
||||||
no-console:
|
no-console:
|
||||||
@ -59,6 +56,8 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-sparse-arrays:
|
no-sparse-arrays:
|
||||||
- error
|
- error
|
||||||
|
no-template-curly-in-string:
|
||||||
|
- error
|
||||||
no-unexpected-multiline:
|
no-unexpected-multiline:
|
||||||
- error
|
- error
|
||||||
no-unreachable:
|
no-unreachable:
|
||||||
@ -93,8 +92,12 @@ rules:
|
|||||||
- error
|
- error
|
||||||
block-scoped-var:
|
block-scoped-var:
|
||||||
- error
|
- error
|
||||||
|
class-methods-use-this:
|
||||||
|
- error
|
||||||
complexity:
|
complexity:
|
||||||
- off
|
- off
|
||||||
|
consistent-return:
|
||||||
|
- error
|
||||||
curly:
|
curly:
|
||||||
- error
|
- error
|
||||||
default-case:
|
default-case:
|
||||||
@ -136,6 +139,8 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-floating-decimal:
|
no-floating-decimal:
|
||||||
- error
|
- error
|
||||||
|
no-global-assign:
|
||||||
|
- error
|
||||||
no-implicit-coercion:
|
no-implicit-coercion:
|
||||||
- error
|
- error
|
||||||
no-implicit-globals:
|
no-implicit-globals:
|
||||||
@ -150,6 +155,8 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-loop-func:
|
no-loop-func:
|
||||||
- error
|
- error
|
||||||
|
no-magic-numbers:
|
||||||
|
- error
|
||||||
no-multi-spaces:
|
no-multi-spaces:
|
||||||
- error
|
- error
|
||||||
no-multi-str:
|
no-multi-str:
|
||||||
@ -166,12 +173,19 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-octal-escape:
|
no-octal-escape:
|
||||||
- error
|
- error
|
||||||
|
no-param-reassign:
|
||||||
|
- error
|
||||||
no-proto:
|
no-proto:
|
||||||
- error
|
- error
|
||||||
no-redeclare:
|
no-redeclare:
|
||||||
- error
|
- error
|
||||||
|
no-restricted-properties:
|
||||||
|
- error
|
||||||
|
- property: __proto__
|
||||||
no-return-assign:
|
no-return-assign:
|
||||||
- error
|
- error
|
||||||
|
no-return-await:
|
||||||
|
- error
|
||||||
no-script-url:
|
no-script-url:
|
||||||
- error
|
- error
|
||||||
no-self-assign:
|
no-self-assign:
|
||||||
@ -184,6 +198,8 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-unmodified-loop-condition:
|
no-unmodified-loop-condition:
|
||||||
- error
|
- error
|
||||||
|
no-unused-expressions:
|
||||||
|
- error
|
||||||
no-unused-labels:
|
no-unused-labels:
|
||||||
- error
|
- error
|
||||||
no-useless-call:
|
no-useless-call:
|
||||||
@ -216,12 +232,18 @@ rules:
|
|||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
|
|
||||||
|
init-declarations:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
no-catch-shadow:
|
no-catch-shadow:
|
||||||
- error
|
- error
|
||||||
no-delete-var:
|
no-delete-var:
|
||||||
- error
|
- error
|
||||||
no-label-var:
|
no-label-var:
|
||||||
- error
|
- error
|
||||||
|
no-restricted-globals:
|
||||||
|
- error
|
||||||
|
- event
|
||||||
no-shadow:
|
no-shadow:
|
||||||
- error
|
- error
|
||||||
no-shadow-restricted-names:
|
no-shadow-restricted-names:
|
||||||
@ -230,6 +252,8 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-undef-init:
|
no-undef-init:
|
||||||
- error
|
- error
|
||||||
|
no-undefined:
|
||||||
|
- error
|
||||||
no-unused-vars:
|
no-unused-vars:
|
||||||
- error
|
- error
|
||||||
no-use-before-define:
|
no-use-before-define:
|
||||||
@ -268,6 +292,13 @@ rules:
|
|||||||
- 1tbs
|
- 1tbs
|
||||||
camelcase:
|
camelcase:
|
||||||
- error
|
- error
|
||||||
|
capitalized-comments:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
|
- ignoreConsecutiveComments: true
|
||||||
|
comma-dangle:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
comma-spacing:
|
comma-spacing:
|
||||||
- error
|
- error
|
||||||
- before: false
|
- before: false
|
||||||
@ -283,6 +314,12 @@ rules:
|
|||||||
- self
|
- self
|
||||||
eol-last:
|
eol-last:
|
||||||
- error
|
- error
|
||||||
|
func-call-spacing:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
|
func-name-matching:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
func-names:
|
func-names:
|
||||||
- error
|
- error
|
||||||
- never
|
- never
|
||||||
@ -291,6 +328,11 @@ rules:
|
|||||||
- expression
|
- expression
|
||||||
id-blacklist:
|
id-blacklist:
|
||||||
- error
|
- error
|
||||||
|
id-length:
|
||||||
|
- error
|
||||||
|
- min: 2
|
||||||
|
exceptions:
|
||||||
|
- "_"
|
||||||
indent:
|
indent:
|
||||||
- error
|
- error
|
||||||
- 2
|
- 2
|
||||||
@ -304,6 +346,12 @@ rules:
|
|||||||
- error
|
- error
|
||||||
- before: true
|
- before: true
|
||||||
after: true
|
after: true
|
||||||
|
line-comment-position:
|
||||||
|
- error
|
||||||
|
- position: above
|
||||||
|
linebreak-style:
|
||||||
|
- error
|
||||||
|
- unix
|
||||||
lines-around-comment:
|
lines-around-comment:
|
||||||
- error
|
- error
|
||||||
- beforeBlockComment: true
|
- beforeBlockComment: true
|
||||||
@ -316,6 +364,9 @@ rules:
|
|||||||
allowObjectEnd: false
|
allowObjectEnd: false
|
||||||
allowArrayStart: true
|
allowArrayStart: true
|
||||||
allowArrayEnd: false
|
allowArrayEnd: false
|
||||||
|
lines-around-directive:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
max-len:
|
max-len:
|
||||||
- error
|
- error
|
||||||
- code: 130
|
- code: 130
|
||||||
@ -323,13 +374,20 @@ rules:
|
|||||||
ignoreComments: false
|
ignoreComments: false
|
||||||
ignoreTrailingComments: false
|
ignoreTrailingComments: false
|
||||||
ignoreUrls: true
|
ignoreUrls: true
|
||||||
|
max-params:
|
||||||
|
- off
|
||||||
max-statements-per-line:
|
max-statements-per-line:
|
||||||
- error
|
- error
|
||||||
- max: 1
|
- max: 1
|
||||||
|
multiline-ternary:
|
||||||
|
- error
|
||||||
|
- never
|
||||||
new-cap:
|
new-cap:
|
||||||
- error
|
- error
|
||||||
new-parens:
|
new-parens:
|
||||||
- error
|
- error
|
||||||
|
newline-per-chained-call:
|
||||||
|
- off
|
||||||
no-array-constructor:
|
no-array-constructor:
|
||||||
- error
|
- error
|
||||||
no-bitwise:
|
no-bitwise:
|
||||||
@ -338,10 +396,14 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-inline-comments:
|
no-inline-comments:
|
||||||
- error
|
- error
|
||||||
|
no-lonely-if:
|
||||||
|
- error
|
||||||
no-mixed-operators:
|
no-mixed-operators:
|
||||||
- error
|
- error
|
||||||
no-mixed-spaces-and-tabs:
|
no-mixed-spaces-and-tabs:
|
||||||
- error
|
- error
|
||||||
|
no-multi-assign:
|
||||||
|
- error
|
||||||
no-multiple-empty-lines:
|
no-multiple-empty-lines:
|
||||||
- error
|
- error
|
||||||
- max: 1
|
- max: 1
|
||||||
@ -355,8 +417,14 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-plusplus:
|
no-plusplus:
|
||||||
- error
|
- error
|
||||||
|
no-restricted-syntax:
|
||||||
|
- error
|
||||||
|
- WithStatement
|
||||||
|
- ForInStatement
|
||||||
no-spaced-func:
|
no-spaced-func:
|
||||||
- error
|
- error
|
||||||
|
no-tabs:
|
||||||
|
- error
|
||||||
no-trailing-spaces:
|
no-trailing-spaces:
|
||||||
- error
|
- error
|
||||||
no-underscore-dangle:
|
no-underscore-dangle:
|
||||||
@ -374,6 +442,9 @@ rules:
|
|||||||
- always
|
- always
|
||||||
object-property-newline:
|
object-property-newline:
|
||||||
- error
|
- error
|
||||||
|
one-var-declaration-per-line:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
one-var:
|
one-var:
|
||||||
- error
|
- error
|
||||||
- never
|
- never
|
||||||
@ -383,6 +454,9 @@ rules:
|
|||||||
operator-linebreak:
|
operator-linebreak:
|
||||||
- error
|
- error
|
||||||
- before
|
- before
|
||||||
|
padded-blocks:
|
||||||
|
- error
|
||||||
|
- classes: always
|
||||||
quote-props:
|
quote-props:
|
||||||
- error
|
- error
|
||||||
- as-needed
|
- as-needed
|
||||||
@ -395,6 +469,7 @@ rules:
|
|||||||
FunctionDeclaration: true
|
FunctionDeclaration: true
|
||||||
ClassDeclaration: true
|
ClassDeclaration: true
|
||||||
MethodDefinition: true
|
MethodDefinition: true
|
||||||
|
ArrowFunctionExpression: true
|
||||||
semi:
|
semi:
|
||||||
- error
|
- error
|
||||||
- always
|
- always
|
||||||
@ -412,9 +487,16 @@ rules:
|
|||||||
- never
|
- never
|
||||||
space-infix-ops:
|
space-infix-ops:
|
||||||
- error
|
- error
|
||||||
|
space-unary-ops:
|
||||||
|
- error
|
||||||
|
- words: true
|
||||||
|
nonwords: false
|
||||||
spaced-comment:
|
spaced-comment:
|
||||||
- error
|
- error
|
||||||
- always
|
- always
|
||||||
|
template-tag-spacing:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
unicode-bom:
|
unicode-bom:
|
||||||
- error
|
- error
|
||||||
|
|
||||||
@ -458,12 +540,24 @@ rules:
|
|||||||
- error
|
- error
|
||||||
no-var:
|
no-var:
|
||||||
- error
|
- error
|
||||||
|
object-shorthand:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
prefer-const:
|
prefer-const:
|
||||||
- error
|
- error
|
||||||
prefer-reflect:
|
prefer-reflect:
|
||||||
- error
|
- error
|
||||||
prefer-spread:
|
prefer-spread:
|
||||||
- error
|
- error
|
||||||
|
prefer-numeric-literals:
|
||||||
|
- error
|
||||||
|
prefer-rest-params:
|
||||||
|
- error
|
||||||
|
prefer-template:
|
||||||
|
- error
|
||||||
|
prefer-arrow-callback:
|
||||||
|
- error
|
||||||
|
- allowNamedFunctions: false
|
||||||
require-yield:
|
require-yield:
|
||||||
- error
|
- error
|
||||||
rest-spread-spacing:
|
rest-spread-spacing:
|
||||||
@ -471,6 +565,8 @@ rules:
|
|||||||
template-curly-spacing:
|
template-curly-spacing:
|
||||||
- error
|
- error
|
||||||
- never
|
- never
|
||||||
|
symbol-description:
|
||||||
|
- error
|
||||||
yield-star-spacing:
|
yield-star-spacing:
|
||||||
- error
|
- error
|
||||||
- before: true
|
- before: true
|
||||||
|
@ -47,7 +47,8 @@ exports.getBooleanArgumentForm = (argumentName, value) => {
|
|||||||
return '--no-';
|
return '--no-';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.size(argumentName) === 1) {
|
const SHORT_OPTION_LENGTH = 1;
|
||||||
|
if (_.size(argumentName) === SHORT_OPTION_LENGTH) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ exports.write = (image, drive, options) => {
|
|||||||
|
|
||||||
const argv = cli.getArguments({
|
const argv = cli.getArguments({
|
||||||
entryPoint: rendererUtils.getApplicationEntryPoint(),
|
entryPoint: rendererUtils.getApplicationEntryPoint(),
|
||||||
image: image,
|
image,
|
||||||
device: drive.device,
|
device: drive.device,
|
||||||
validateWriteOnSuccess: options.validateWriteOnSuccess,
|
validateWriteOnSuccess: options.validateWriteOnSuccess,
|
||||||
unmountOnSuccess: options.unmountOnSuccess
|
unmountOnSuccess: options.unmountOnSuccess
|
||||||
@ -77,6 +77,14 @@ exports.write = (image, drive, options) => {
|
|||||||
ipc.config.silent = true;
|
ipc.config.silent = true;
|
||||||
ipc.serve();
|
ipc.serve();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Safely terminate the IPC server
|
||||||
|
* @function
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* terminateServer();
|
||||||
|
*/
|
||||||
const terminateServer = () => {
|
const terminateServer = () => {
|
||||||
|
|
||||||
// Turns out we need to destroy all sockets for
|
// Turns out we need to destroy all sockets for
|
||||||
@ -90,6 +98,16 @@ exports.write = (image, drive, options) => {
|
|||||||
ipc.server.stop();
|
ipc.server.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Emit an error to the client
|
||||||
|
* @function
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {Error} error - error
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* emitError(new Error('foo bar'));
|
||||||
|
*/
|
||||||
const emitError = (error) => {
|
const emitError = (error) => {
|
||||||
terminateServer();
|
terminateServer();
|
||||||
emitter.emit('error', error);
|
emitter.emit('error', error);
|
||||||
@ -97,7 +115,7 @@ exports.write = (image, drive, options) => {
|
|||||||
|
|
||||||
ipc.server.on('error', emitError);
|
ipc.server.on('error', emitError);
|
||||||
ipc.server.on('message', (data) => {
|
ipc.server.on('message', (data) => {
|
||||||
let message;
|
let message = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
message = robot.parseMessage(data);
|
message = robot.parseMessage(data);
|
||||||
@ -112,7 +130,7 @@ exports.write = (image, drive, options) => {
|
|||||||
return emitError(robot.recomposeErrorMessage(message));
|
return emitError(robot.recomposeErrorMessage(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.emit(robot.getCommand(message), robot.getData(message));
|
return emitter.emit(robot.getCommand(message), robot.getData(message));
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.server.on('start', () => {
|
ipc.server.on('start', () => {
|
||||||
@ -144,9 +162,13 @@ exports.write = (image, drive, options) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code !== EXIT_CODES.SUCCESS && code !== EXIT_CODES.VALIDATION_ERROR) {
|
// We shouldn't emit the `done` event manually here
|
||||||
return emitError(new Error(`Child process exited with error code: ${code}`));
|
// since the writer process will take care of it.
|
||||||
|
if (code === EXIT_CODES.SUCCESS || code === EXIT_CODES.VALIDATION_ERROR) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return emitError(new Error(`Child process exited with error code: ${code}`));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,9 +40,12 @@ exports.getApplicationEntryPoint = () => {
|
|||||||
return path.join(process.resourcesPath, 'app.asar');
|
return path.join(process.resourcesPath, 'app.asar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ENTRY_POINT_ARGV_INDEX = 1;
|
||||||
|
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, electron.remote.process.argv[1]);
|
return path.join(CONSTANTS.PROJECT_ROOT, relativeEntryPoint);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -37,8 +37,32 @@ const packageJSON = require('../../package.json');
|
|||||||
// and `stderr` to the parent process using IPC communication,
|
// and `stderr` to the parent process using IPC communication,
|
||||||
// taking care of the writer elevation as needed.
|
// taking care of the writer elevation as needed.
|
||||||
|
|
||||||
const EXECUTABLE = process.argv[0];
|
/**
|
||||||
const ETCHER_ARGUMENTS = process.argv.slice(2);
|
* @summary The Etcher executable file path
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
const executable = _.first(process.argv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The first index that represents an actual option argument
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* The first arguments are usually the program executable itself, etc.
|
||||||
|
*/
|
||||||
|
const OPTIONS_INDEX_START = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The list of Etcher argument options
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {String[]}
|
||||||
|
*/
|
||||||
|
const etcherArguments = process.argv.slice(OPTIONS_INDEX_START);
|
||||||
|
|
||||||
return isElevated().then((elevated) => {
|
return isElevated().then((elevated) => {
|
||||||
|
|
||||||
@ -153,7 +177,7 @@ return isElevated().then((elevated) => {
|
|||||||
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, ETCHER_ARGUMENTS, {
|
const child = childProcess.spawn(executable, etcherArguments, {
|
||||||
env: {
|
env: {
|
||||||
|
|
||||||
// The CLI might call operating system utilities (like `diskutil`),
|
// The CLI might call operating system utilities (like `diskutil`),
|
||||||
@ -169,6 +193,18 @@ return isElevated().then((elevated) => {
|
|||||||
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
|
||||||
|
* @function
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {Buffer} data - json message data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* emitMessage(Buffer.from(JSON.stringify({
|
||||||
|
* foo: 'bar'
|
||||||
|
* })));
|
||||||
|
*/
|
||||||
const emitMessage = (data) => {
|
const emitMessage = (data) => {
|
||||||
|
|
||||||
// Output from stdout/stderr coming from the CLI might be buffered,
|
// Output from stdout/stderr coming from the CLI might be buffered,
|
||||||
|
@ -108,7 +108,7 @@ exports.getErrorMessage = (error) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (error.description) {
|
if (error.description) {
|
||||||
return message + '\n\n' + error.description;
|
return `${message}\n\n${error.description}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
@ -29,6 +29,9 @@ 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 ARGV_IMAGE_PATH_INDEX = 0;
|
||||||
|
const imagePath = options._[ARGV_IMAGE_PATH_INDEX];
|
||||||
|
|
||||||
isElevated().then((elevated) => {
|
isElevated().then((elevated) => {
|
||||||
if (!elevated) {
|
if (!elevated) {
|
||||||
throw new Error(messages.error.elevationRequired());
|
throw new Error(messages.error.elevationRequired());
|
||||||
@ -50,10 +53,10 @@ isElevated().then((elevated) => {
|
|||||||
override: {
|
override: {
|
||||||
drive: options.drive,
|
drive: options.drive,
|
||||||
|
|
||||||
// If `options.yes` is `false`, pass `undefined`,
|
// If `options.yes` is `false`, pass `null`,
|
||||||
// otherwise the question will not be asked because
|
// otherwise the question will not be asked because
|
||||||
// `false` is a defined value.
|
// `false` is a defined value.
|
||||||
yes: robot.isEnabled(process.env) || options.yes || undefined
|
yes: robot.isEnabled(process.env) || options.yes || null
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -76,7 +79,7 @@ isElevated().then((elevated) => {
|
|||||||
throw new Error(`Drive not found: ${answers.drive}`);
|
throw new Error(`Drive not found: ${answers.drive}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return writer.writeImage(options._[0], selectedDrive, {
|
return writer.writeImage(imagePath, selectedDrive, {
|
||||||
unmountOnSuccess: options.unmount,
|
unmountOnSuccess: options.unmount,
|
||||||
validateWriteOnSuccess: options.check
|
validateWriteOnSuccess: options.check
|
||||||
}, (state) => {
|
}, (state) => {
|
||||||
@ -109,6 +112,7 @@ isElevated().then((elevated) => {
|
|||||||
console.log(`Checksum: ${results.sourceChecksum}`);
|
console.log(`Checksum: ${results.sourceChecksum}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Bluebird.resolve();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
process.exit(EXIT_CODES.SUCCESS);
|
process.exit(EXIT_CODES.SUCCESS);
|
||||||
});
|
});
|
||||||
@ -121,6 +125,7 @@ isElevated().then((elevated) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errors.print(error);
|
errors.print(error);
|
||||||
|
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);
|
||||||
|
@ -24,6 +24,33 @@ const robot = require('../shared/robot');
|
|||||||
const EXIT_CODES = require('../shared/exit-codes');
|
const EXIT_CODES = require('../shared/exit-codes');
|
||||||
const packageJSON = require('../../package.json');
|
const packageJSON = require('../../package.json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The minimum required number of CLI arguments
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
const MINIMUM_NUMBER_OF_ARGUMENTS = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The index of the image argument
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
const IMAGE_PATH_ARGV_INDEX = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The first index that represents an actual option argument
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* The first arguments are usually the program executable itself, etc.
|
||||||
|
*/
|
||||||
|
const OPTIONS_INDEX_START = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Parsed CLI options and arguments
|
* @summary Parsed CLI options and arguments
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
@ -34,7 +61,7 @@ module.exports = yargs
|
|||||||
// Don't wrap at all
|
// Don't wrap at all
|
||||||
.wrap(null)
|
.wrap(null)
|
||||||
|
|
||||||
.demand(1, 'Missing image')
|
.demand(MINIMUM_NUMBER_OF_ARGUMENTS, 'Missing image')
|
||||||
|
|
||||||
// Usage help
|
// Usage help
|
||||||
.usage('Usage: $0 [options] <image>')
|
.usage('Usage: $0 [options] <image>')
|
||||||
@ -42,7 +69,7 @@ module.exports = yargs
|
|||||||
'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:',
|
||||||
@ -64,7 +91,7 @@ module.exports = yargs
|
|||||||
.version(_.constant(packageJSON.version))
|
.version(_.constant(packageJSON.version))
|
||||||
|
|
||||||
// Error reporting
|
// Error reporting
|
||||||
.fail(function(message, error) {
|
.fail((message, error) => {
|
||||||
if (robot.isEnabled(process.env)) {
|
if (robot.isEnabled(process.env)) {
|
||||||
robot.printError(error || message);
|
robot.printError(error || message);
|
||||||
} else {
|
} else {
|
||||||
@ -72,12 +99,12 @@ module.exports = yargs
|
|||||||
errors.print(error || message);
|
errors.print(error || message);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
||||||
})
|
})
|
||||||
|
|
||||||
// Assert that image exists
|
// Assert that image exists
|
||||||
.check((argv) => {
|
.check((argv) => {
|
||||||
fs.accessSync(argv._[0]);
|
fs.accessSync(argv._[IMAGE_PATH_ARGV_INDEX]);
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -123,4 +150,4 @@ module.exports = yargs
|
|||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.parse(process.argv.slice(2));
|
.parse(process.argv.slice(OPTIONS_INDEX_START));
|
||||||
|
@ -59,7 +59,7 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
|
|||||||
|
|
||||||
// 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;
|
return Bluebird.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return unmount.unmountDrive(drive);
|
return unmount.unmountDrive(drive);
|
||||||
@ -96,7 +96,7 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
|
|||||||
return fs.closeAsync(driveFileDescriptor).then(() => {
|
return fs.closeAsync(driveFileDescriptor).then(() => {
|
||||||
|
|
||||||
if (!options.unmountOnSuccess) {
|
if (!options.unmountOnSuccess) {
|
||||||
return;
|
return Bluebird.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return unmount.unmountDrive(drive);
|
return unmount.unmountDrive(drive);
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
var angular = require('angular');
|
var angular = require('angular');
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
|
const Bluebird = require('bluebird');
|
||||||
const EXIT_CODES = require('../shared/exit-codes');
|
const EXIT_CODES = require('../shared/exit-codes');
|
||||||
const messages = require('../shared/messages');
|
const messages = require('../shared/messages');
|
||||||
|
|
||||||
@ -98,6 +99,8 @@ app.run((AnalyticsService, ErrorService, UpdateNotifierService, SelectionStateMo
|
|||||||
AnalyticsService.logEvent('Notifying update');
|
AnalyticsService.logEvent('Notifying update');
|
||||||
return UpdateNotifierService.notify();
|
return UpdateNotifierService.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Bluebird.resolve();
|
||||||
}).catch(ErrorService.reportException);
|
}).catch(ErrorService.reportException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +161,7 @@ app.run(($window, WarningModalService, ErrorService, FlashStateModel, OSDialogSe
|
|||||||
// Don't open any more popups
|
// Don't open any more popups
|
||||||
popupExists = true;
|
popupExists = true;
|
||||||
|
|
||||||
return OSDialogService.showWarning({
|
OSDialogService.showWarning({
|
||||||
confirmationLabel: 'Yes, quit',
|
confirmationLabel: 'Yes, quit',
|
||||||
rejectionLabel: 'Cancel',
|
rejectionLabel: 'Cancel',
|
||||||
title: 'Are you sure you want to close Etcher?',
|
title: 'Are you sure you want to close Etcher?',
|
||||||
|
@ -82,7 +82,7 @@ module.exports = function(
|
|||||||
description: [
|
description: [
|
||||||
messages.warning.unrecommendedDriveSize({
|
messages.warning.unrecommendedDriveSize({
|
||||||
image: SelectionStateModel.getImage(),
|
image: SelectionStateModel.getImage(),
|
||||||
drive: drive
|
drive
|
||||||
}),
|
}),
|
||||||
'Are you sure you want to continue?'
|
'Are you sure you want to continue?'
|
||||||
].join(' ')
|
].join(' ')
|
||||||
|
@ -59,18 +59,12 @@ module.exports = function($uibModal, $q) {
|
|||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
||||||
// Bootstrap doesn't 'resolve' these but cancels the dialog;
|
// Bootstrap doesn't 'resolve' these but cancels the dialog
|
||||||
// therefore call 'resolve' here applied to 'false'.
|
|
||||||
if (error === 'escape key press' || error === 'backdrop click') {
|
if (error === 'escape key press' || error === 'backdrop click') {
|
||||||
resolve();
|
return resolve();
|
||||||
|
|
||||||
// For some annoying reason, UI Bootstrap Modal rejects
|
|
||||||
// the result reason if the user clicks on the backdrop
|
|
||||||
// (e.g: the area surrounding the modal).
|
|
||||||
} else if (error !== 'backdrop click') {
|
|
||||||
return reject(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return reject(error);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports = function($uibModalInstance, SettingsModel, options) {
|
module.exports = function($uibModalInstance, SettingsModel, UPDATE_NOTIFIER_SLEEP_DAYS, options) {
|
||||||
|
|
||||||
// We update this value in this controller since its the only place
|
// We update this value in this controller since its the only place
|
||||||
// where we can be sure the modal was really presented to the user.
|
// where we can be sure the modal was really presented to the user.
|
||||||
@ -25,6 +25,14 @@ module.exports = function($uibModalInstance, SettingsModel, options) {
|
|||||||
// have been called, but the modal could have failed to be shown.
|
// have been called, but the modal could have failed to be shown.
|
||||||
SettingsModel.set('lastUpdateNotify', Date.now());
|
SettingsModel.set('lastUpdateNotify', Date.now());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The number of days the update notified can be put to sleep
|
||||||
|
* @constant
|
||||||
|
* @public
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
this.sleepDays = UPDATE_NOTIFIER_SLEEP_DAYS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Settings model
|
* @summary Settings model
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
@ -19,8 +19,9 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const etcherLatestVersion = require('etcher-latest-version');
|
const etcherLatestVersion = require('etcher-latest-version');
|
||||||
|
const units = require('../../../../shared/units');
|
||||||
|
|
||||||
module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_TIME, ManifestBindService, SettingsModel) {
|
module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_DAYS, ManifestBindService, SettingsModel) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary The current application version
|
* @summary The current application version
|
||||||
@ -57,10 +58,12 @@ module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_TIME, M
|
|||||||
}, (error, latestVersion) => {
|
}, (error, latestVersion) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
||||||
// The error status equals -1 if the request couldn't
|
// The error status equals this number if the request
|
||||||
// be made successfully, for example, because of a
|
// couldn't be made successfuly, for example, because
|
||||||
// timeout on an unstable network connection.
|
// of a timeout on an unstable network connection.
|
||||||
if (error.status === -1) {
|
const ERROR_CODE_UNSUCCESSFUL_REQUEST = -1;
|
||||||
|
|
||||||
|
if (error.status === ERROR_CODE_UNSUCCESSFUL_REQUEST) {
|
||||||
return resolve(CURRENT_VERSION);
|
return resolve(CURRENT_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +117,7 @@ module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_TIME, M
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastUpdateNotify - Date.now() > UPDATE_NOTIFIER_SLEEP_TIME) {
|
if (lastUpdateNotify - Date.now() > units.daysToMilliseconds(UPDATE_NOTIFIER_SLEEP_DAYS)) {
|
||||||
SettingsModel.set('sleepUpdateCheck', false);
|
SettingsModel.set('sleepUpdateCheck', false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -140,7 +143,7 @@ module.exports = function($http, $q, ModalService, UPDATE_NOTIFIER_SLEEP_TIME, M
|
|||||||
size: 'update-notifier',
|
size: 'update-notifier',
|
||||||
resolve: {
|
resolve: {
|
||||||
options: _.constant({
|
options: _.constant({
|
||||||
version: version
|
version
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).result;
|
}).result;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
ng-model="modal.sleepUpdateCheck"
|
ng-model="modal.sleepUpdateCheck"
|
||||||
ng-change="modal.settings.set('sleepUpdateCheck', modal.sleepUpdateCheck)">
|
ng-change="modal.settings.set('sleepUpdateCheck', modal.sleepUpdateCheck)">
|
||||||
<span>Remind me again in 7 days</span>
|
<span>Remind me again in {{::modal.sleepDays}} days</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,15 @@ const UpdateNotifier = angular.module(MODULE_NAME, [
|
|||||||
require('../../os/open-external/open-external')
|
require('../../os/open-external/open-external')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_TIME', 7 * 24 * 60 * 60 * 100);
|
/**
|
||||||
|
* @summary The number of days the update notifier can be put to sleep
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
const UPDATE_NOTIFIER_SLEEP_DAYS = 7;
|
||||||
|
|
||||||
|
UpdateNotifier.constant('UPDATE_NOTIFIER_SLEEP_DAYS', UPDATE_NOTIFIER_SLEEP_DAYS);
|
||||||
UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier'));
|
UpdateNotifier.controller('UpdateNotifierController', require('./controllers/update-notifier'));
|
||||||
UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier'));
|
UpdateNotifier.service('UpdateNotifierService', require('./services/update-notifier'));
|
||||||
|
|
||||||
|
@ -41,12 +41,13 @@ electron.app.on('ready', () => {
|
|||||||
// Prevent flash of white when starting the application
|
// Prevent flash of white when starting the application
|
||||||
// https://github.com/atom/electron/issues/2172
|
// https://github.com/atom/electron/issues/2172
|
||||||
mainWindow.webContents.on('did-finish-load', () => {
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
|
const WEBVIEW_LOAD_TIMEOUT_MS = 100;
|
||||||
|
|
||||||
// The flash of white is still present for a very short
|
// The flash of white is still present for a very short
|
||||||
// while after the WebView reports it finished loading
|
// while after the WebView reports it finished loading
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
}, 100);
|
}, WEBVIEW_LOAD_TIMEOUT_MS);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,19 +64,36 @@ Drives.service('DrivesModel', function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// This workaround is needed to avoid AngularJS from getting
|
/**
|
||||||
// caught in an infinite digest loop when using `ngRepeat`
|
* @summary Memoize ImmutableJS list reference
|
||||||
// over a function that returns a mutable version of an
|
* @function
|
||||||
// ImmutableJS object.
|
* @private
|
||||||
//
|
*
|
||||||
// The problem is that every time you call `myImmutableObject.toJS()`
|
* @description
|
||||||
// you will get a new object, whose reference is different from
|
* This workaround is needed to avoid AngularJS from getting
|
||||||
// the one you previously got, even if the data is exactly the same.
|
* caught in an infinite digest loop when using `ngRepeat`
|
||||||
|
* over a function that returns a mutable version of an
|
||||||
|
* ImmutableJS object.
|
||||||
|
*
|
||||||
|
* The problem is that every time you call `myImmutableObject.toJS()`
|
||||||
|
* you will get a new object, whose reference is different from
|
||||||
|
* the one you previously got, even if the data is exactly the same.
|
||||||
|
*
|
||||||
|
* @param {Function} func - function that returns an ImmutableJS list
|
||||||
|
* @returns {Function} memoized function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const getList = () => {
|
||||||
|
* return Store.getState().toJS().myList;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* const memoizedFunction = memoizeImmutableListReference(getList);
|
||||||
|
*/
|
||||||
const memoizeImmutableListReference = (func) => {
|
const memoizeImmutableListReference = (func) => {
|
||||||
let previous = [];
|
let previous = [];
|
||||||
|
|
||||||
return () => {
|
return (...args) => {
|
||||||
const list = Reflect.apply(func, this, arguments);
|
const list = Reflect.apply(func, this, args);
|
||||||
|
|
||||||
if (!_.isEqual(list, previous)) {
|
if (!_.isEqual(list, previous)) {
|
||||||
previous = list;
|
previous = list;
|
||||||
|
@ -134,9 +134,12 @@ FlashState.service('FlashStateModel', function() {
|
|||||||
if (_.isNumber(state.speed) && !_.isNaN(state.speed)) {
|
if (_.isNumber(state.speed) && !_.isNaN(state.speed)) {
|
||||||
|
|
||||||
// Preserve only two decimal places
|
// Preserve only two decimal places
|
||||||
return Math.floor(units.bytesToMegabytes(state.speed) * 100) / 100;
|
const PRECISION = 2;
|
||||||
|
return _.round(units.bytesToMegabytes(state.speed), PRECISION);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -286,9 +286,7 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
|
|||||||
* @example
|
* @example
|
||||||
* SelectionStateModel.clear({ preserveImage: true });
|
* SelectionStateModel.clear({ preserveImage: true });
|
||||||
*/
|
*/
|
||||||
this.clear = (options) => {
|
this.clear = (options = {}) => {
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
if (!options.preserveImage) {
|
if (!options.preserveImage) {
|
||||||
Store.dispatch({
|
Store.dispatch({
|
||||||
type: Store.Actions.REMOVE_IMAGE
|
type: Store.Actions.REMOVE_IMAGE
|
||||||
|
@ -75,9 +75,21 @@ const ACTIONS = _.fromPairs(_.map([
|
|||||||
return [ message, message ];
|
return [ message, message ];
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const storeReducer = (state, action) => {
|
/**
|
||||||
state = state || DEFAULT_STATE;
|
* @summary The redux store reducer
|
||||||
|
* @function
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {Object} state - application state
|
||||||
|
* @param {Object} action - dispatched action
|
||||||
|
* @returns {Object} new application state
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const newState = storeReducer(DEFAULT_STATE, {
|
||||||
|
* type: ACTIONS.REMOVE_DRIVE
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
const storeReducer = (state = DEFAULT_STATE, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case ACTIONS.SET_AVAILABLE_DRIVES: {
|
case ACTIONS.SET_AVAILABLE_DRIVES: {
|
||||||
@ -91,7 +103,9 @@ const storeReducer = (state, action) => {
|
|||||||
|
|
||||||
const newState = state.set('availableDrives', Immutable.fromJS(action.data));
|
const newState = state.set('availableDrives', Immutable.fromJS(action.data));
|
||||||
|
|
||||||
if (action.data.length === 1) {
|
const AUTOSELECT_DRIVE_COUNT = 1;
|
||||||
|
const numberOfDrives = action.data.length;
|
||||||
|
if (numberOfDrives === AUTOSELECT_DRIVE_COUNT) {
|
||||||
|
|
||||||
const drive = _.first(action.data);
|
const drive = _.first(action.data);
|
||||||
|
|
||||||
@ -150,7 +164,7 @@ const storeReducer = (state, action) => {
|
|||||||
throw new Error(`Invalid state percentage: ${action.data.percentage}`);
|
throw new Error(`Invalid state percentage: ${action.data.percentage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!action.data.eta && action.data.eta !== 0) {
|
if (_.isNil(action.data.eta)) {
|
||||||
throw new Error('Missing state eta');
|
throw new Error('Missing state eta');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,9 +366,9 @@ module.exports = _.merge(redux.createStore(
|
|||||||
// In the first run, there will be no information
|
// In the first run, there will be no information
|
||||||
// to deserialize. In this case, we avoid merging,
|
// to deserialize. In this case, we avoid merging,
|
||||||
// otherwise we will be basically erasing the property
|
// otherwise we will be basically erasing the property
|
||||||
// we aim the keep serialising the in future.
|
// we aim to keep serialising the in future.
|
||||||
if (!subset) {
|
if (!subset) {
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blindly setting the state to the deserialised subset
|
// Blindly setting the state to the deserialised subset
|
||||||
|
@ -115,7 +115,7 @@ SupportedFormats.service('SupportedFormatsModel', function() {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
this.isSupportedImage = (imagePath) => {
|
this.isSupportedImage = (imagePath) => {
|
||||||
const extension = path.extname(imagePath).slice(1).toLowerCase();
|
const extension = _.replace(path.extname(imagePath), '.', '').toLowerCase();
|
||||||
|
|
||||||
if (_.some([
|
if (_.some([
|
||||||
_.includes(this.getNonCompressedExtensions(), extension),
|
_.includes(this.getNonCompressedExtensions(), extension),
|
||||||
|
@ -93,13 +93,13 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel, Setting
|
|||||||
* AnalyticsService.log('Hello World');
|
* AnalyticsService.log('Hello World');
|
||||||
*/
|
*/
|
||||||
this.logDebug = (message) => {
|
this.logDebug = (message) => {
|
||||||
message = new Date() + ' ' + message;
|
const debugMessage = `${new Date()} ${message}`;
|
||||||
|
|
||||||
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
||||||
$window.trackJs.console.debug(message);
|
$window.trackJs.console.debug(debugMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
$log.debug(message);
|
$log.debug(debugMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,7 +119,6 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel, Setting
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
this.logEvent = (message, data) => {
|
this.logEvent = (message, data) => {
|
||||||
|
|
||||||
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
if (SettingsModel.get('errorReporting') && isRunningInAsar()) {
|
||||||
|
|
||||||
// Clone data before passing it to `mixpanel.track`
|
// Clone data before passing it to `mixpanel.track`
|
||||||
@ -129,11 +128,15 @@ analytics.service('AnalyticsService', function($log, $window, $mixpanel, Setting
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const debugMessage = _.attempt(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
message += ` (${JSON.stringify(data)})`;
|
return `${message} (${JSON.stringify(data)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logDebug(message);
|
return message;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logDebug(debugMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,16 +34,20 @@ const driveScanner = angular.module(MODULE_NAME, [
|
|||||||
|
|
||||||
driveScanner.factory('DriveScannerService', (SettingsModel) => {
|
driveScanner.factory('DriveScannerService', (SettingsModel) => {
|
||||||
const DRIVE_SCANNER_INTERVAL_MS = 2000;
|
const DRIVE_SCANNER_INTERVAL_MS = 2000;
|
||||||
|
const DRIVE_SCANNER_FIRST_SCAN_DELAY_MS = 0;
|
||||||
const emitter = new EventEmitter();
|
const emitter = new EventEmitter();
|
||||||
|
|
||||||
const availableDrives = Rx.Observable.timer(0, DRIVE_SCANNER_INTERVAL_MS)
|
const availableDrives = Rx.Observable.timer(
|
||||||
|
DRIVE_SCANNER_FIRST_SCAN_DELAY_MS,
|
||||||
|
DRIVE_SCANNER_INTERVAL_MS
|
||||||
|
)
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
return Rx.Observable.fromNodeCallback(drivelist.list)();
|
return Rx.Observable.fromNodeCallback(drivelist.list)();
|
||||||
})
|
})
|
||||||
.map((drives) => {
|
|
||||||
|
|
||||||
// Calculate an appropriate "display name"
|
// Build human friendly "description"
|
||||||
drives = _.map(drives, (drive) => {
|
.map((drives) => {
|
||||||
|
return _.map(drives, (drive) => {
|
||||||
drive.name = drive.device;
|
drive.name = drive.device;
|
||||||
|
|
||||||
if (os.platform() === 'win32' && !_.isEmpty(drive.mountpoints)) {
|
if (os.platform() === 'win32' && !_.isEmpty(drive.mountpoints)) {
|
||||||
@ -52,7 +56,9 @@ driveScanner.factory('DriveScannerService', (SettingsModel) => {
|
|||||||
|
|
||||||
return drive;
|
return drive;
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.map((drives) => {
|
||||||
if (SettingsModel.get('unsafeMode')) {
|
if (SettingsModel.get('unsafeMode')) {
|
||||||
return drives;
|
return drives;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ module.exports = function($q, SupportedFormatsModel) {
|
|||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
imageStream.getImageMetadata(imagePath).then((metadata) => {
|
return imageStream.getImageMetadata(imagePath).then((metadata) => {
|
||||||
metadata.path = imagePath;
|
metadata.path = imagePath;
|
||||||
metadata.size = metadata.size.final.value;
|
metadata.size = metadata.size.final.value;
|
||||||
return resolve(metadata);
|
return resolve(metadata);
|
||||||
@ -95,8 +95,8 @@ module.exports = function($q, SupportedFormatsModel) {
|
|||||||
* @param {Object} options - options
|
* @param {Object} options - options
|
||||||
* @param {String} options.title - dialog title
|
* @param {String} options.title - dialog title
|
||||||
* @param {String} options.description - dialog description
|
* @param {String} options.description - dialog description
|
||||||
* @param {String} options.confirmationLabel - confirmation label
|
* @param {String} [options.confirmationLabel="OK"] - confirmation label
|
||||||
* @param {String} options.rejectionLabel - rejection label
|
* @param {String} [options.rejectionLabel="Cancel"] - rejection label
|
||||||
* @fulfil {Boolean} - whether the dialog was confirmed or not
|
* @fulfil {Boolean} - whether the dialog was confirmed or not
|
||||||
* @returns {Promise};
|
* @returns {Promise};
|
||||||
*
|
*
|
||||||
@ -113,20 +113,30 @@ module.exports = function($q, SupportedFormatsModel) {
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
this.showWarning = (options) => {
|
this.showWarning = (options) => {
|
||||||
|
_.defaults(options, {
|
||||||
|
confirmationLabel: 'OK',
|
||||||
|
rejectionLabel: 'Cancel'
|
||||||
|
});
|
||||||
|
|
||||||
|
const BUTTONS = [
|
||||||
|
options.confirmationLabel,
|
||||||
|
options.rejectionLabel
|
||||||
|
];
|
||||||
|
|
||||||
|
const BUTTON_CONFIRMATION_INDEX = _.indexOf(BUTTONS, options.confirmationLabel);
|
||||||
|
const BUTTON_REJECTION_INDEX = _.indexOf(BUTTONS, options.rejectionLabel);
|
||||||
|
|
||||||
return $q((resolve) => {
|
return $q((resolve) => {
|
||||||
electron.remote.dialog.showMessageBox(currentWindow, {
|
electron.remote.dialog.showMessageBox(currentWindow, {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
buttons: [
|
buttons: BUTTONS,
|
||||||
options.confirmationLabel,
|
defaultId: BUTTON_REJECTION_INDEX,
|
||||||
options.rejectionLabel
|
cancelId: BUTTON_REJECTION_INDEX,
|
||||||
],
|
|
||||||
defaultId: 1,
|
|
||||||
cancelId: 1,
|
|
||||||
title: 'Attention',
|
title: 'Attention',
|
||||||
message: options.title,
|
message: options.title,
|
||||||
detail: options.description
|
detail: options.description
|
||||||
}, (response) => {
|
}, (response) => {
|
||||||
return resolve(response === 0);
|
return resolve(response === BUTTON_CONFIRMATION_INDEX);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -149,19 +159,19 @@ module.exports = function($q, SupportedFormatsModel) {
|
|||||||
* OSDialogService.showError('Foo Bar', 'An error happened!');
|
* OSDialogService.showError('Foo Bar', 'An error happened!');
|
||||||
*/
|
*/
|
||||||
this.showError = (error, description) => {
|
this.showError = (error, description) => {
|
||||||
error = error || {};
|
const errorObject = error || {};
|
||||||
|
|
||||||
// Try to get as most information as possible about the error
|
// Try to get as most information as possible about the error
|
||||||
// rather than falling back to generic messages right away.
|
// rather than falling back to generic messages right away.
|
||||||
const title = _.attempt(() => {
|
const title = _.attempt(() => {
|
||||||
if (_.isString(error)) {
|
if (_.isString(errorObject)) {
|
||||||
return error;
|
return errorObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
return error.message || error.code || 'An error ocurred';
|
return errorObject.message || errorObject.code || 'An error ocurred';
|
||||||
});
|
});
|
||||||
|
|
||||||
const message = description || error.stack || JSON.stringify(error) || '';
|
const message = description || errorObject.stack || JSON.stringify(errorObject) || '';
|
||||||
|
|
||||||
// Ensure the parameters are strings to prevent the following
|
// Ensure the parameters are strings to prevent the following
|
||||||
// types of obscure errors:
|
// types of obscure errors:
|
||||||
|
@ -40,8 +40,8 @@ module.exports = ($timeout) => {
|
|||||||
scope: {
|
scope: {
|
||||||
osDropzone: '&'
|
osDropzone: '&'
|
||||||
},
|
},
|
||||||
link: (scope, element) => {
|
link: (scope, $element) => {
|
||||||
const domElement = element[0];
|
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
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ module.exports = ($timeout) => {
|
|||||||
|
|
||||||
domElement.ondrop = (event) => {
|
domElement.ondrop = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const filename = event.dataTransfer.files[0].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(() => {
|
||||||
|
@ -48,7 +48,7 @@ module.exports = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Notification(title, {
|
return new Notification(title, {
|
||||||
body: body
|
body
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ 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(function(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)) {
|
||||||
|
@ -26,12 +26,9 @@ module.exports = function() {
|
|||||||
* @protected
|
* @protected
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* Since electron only has one renderer view, we can assume the
|
|
||||||
* current window is the one with id == 1.
|
|
||||||
*
|
|
||||||
* We expose this property to `this` for testability purposes.
|
* We expose this property to `this` for testability purposes.
|
||||||
*/
|
*/
|
||||||
this.currentWindow = electron.remote.BrowserWindow.fromId(1);
|
this.currentWindow = electron.remote.getCurrentWindow();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Set operating system window progress
|
* @summary Set operating system window progress
|
||||||
@ -47,11 +44,14 @@ module.exports = function() {
|
|||||||
* OSWindowProgressService.set(85);
|
* OSWindowProgressService.set(85);
|
||||||
*/
|
*/
|
||||||
this.set = (percentage) => {
|
this.set = (percentage) => {
|
||||||
if (percentage > 100 || percentage < 0) {
|
const PERCENTAGE_MINIMUM = 0;
|
||||||
|
const PERCENTAGE_MAXIMUM = 100;
|
||||||
|
|
||||||
|
if (percentage > PERCENTAGE_MAXIMUM || percentage < PERCENTAGE_MINIMUM) {
|
||||||
throw new Error(`Invalid window progress percentage: ${percentage}`);
|
throw new Error(`Invalid window progress percentage: ${percentage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentWindow.setProgressBar(percentage / 100);
|
this.currentWindow.setProgressBar(percentage / PERCENTAGE_MAXIMUM);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,8 +65,9 @@ module.exports = function() {
|
|||||||
this.clear = () => {
|
this.clear = () => {
|
||||||
|
|
||||||
// Passing 0 or null/undefined doesn't work.
|
// Passing 0 or null/undefined doesn't work.
|
||||||
this.currentWindow.setProgressBar(-1);
|
const ELECTRON_PROGRESS_BAR_RESET_VALUE = -1;
|
||||||
|
|
||||||
|
this.currentWindow.setProgressBar(ELECTRON_PROGRESS_BAR_RESET_VALUE);
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -58,7 +58,7 @@ module.exports = function(
|
|||||||
DriveScannerService.stop();
|
DriveScannerService.stop();
|
||||||
|
|
||||||
AnalyticsService.logEvent('Flash', {
|
AnalyticsService.logEvent('Flash', {
|
||||||
image: image,
|
image,
|
||||||
device: drive.device
|
device: drive.device
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,22 +106,20 @@ module.exports = function(
|
|||||||
this.getProgressButtonLabel = () => {
|
this.getProgressButtonLabel = () => {
|
||||||
const flashState = FlashStateModel.getFlashState();
|
const flashState = FlashStateModel.getFlashState();
|
||||||
const isChecking = flashState.type === 'check';
|
const isChecking = flashState.type === 'check';
|
||||||
|
const PERCENTAGE_MINIMUM = 0;
|
||||||
|
const PERCENTAGE_MAXIMUM = 100;
|
||||||
|
|
||||||
if (!FlashStateModel.isFlashing()) {
|
if (!FlashStateModel.isFlashing()) {
|
||||||
return 'Flash!';
|
return 'Flash!';
|
||||||
}
|
} else if (flashState.percentage === PERCENTAGE_MINIMUM && !flashState.speed) {
|
||||||
|
|
||||||
if (flashState.percentage === 0 && !flashState.speed) {
|
|
||||||
return 'Starting...';
|
return 'Starting...';
|
||||||
} else if (flashState.percentage === 100) {
|
} else if (flashState.percentage === PERCENTAGE_MAXIMUM) {
|
||||||
if (isChecking && SettingsModel.get('unmountOnSuccess')) {
|
if (isChecking && SettingsModel.get('unmountOnSuccess')) {
|
||||||
return 'Unmounting...';
|
return 'Unmounting...';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Finishing...';
|
return 'Finishing...';
|
||||||
}
|
} else if (isChecking) {
|
||||||
|
|
||||||
if (isChecking) {
|
|
||||||
return `${flashState.percentage}% Validating...`;
|
return `${flashState.percentage}% Validating...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ module.exports = function(
|
|||||||
this.selectImage = (image) => {
|
this.selectImage = (image) => {
|
||||||
if (!SupportedFormatsModel.isSupportedImage(image.path)) {
|
if (!SupportedFormatsModel.isSupportedImage(image.path)) {
|
||||||
OSDialogService.showError('Invalid image', messages.error.invalidImage({
|
OSDialogService.showError('Invalid image', messages.error.invalidImage({
|
||||||
image: image
|
image
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AnalyticsService.logEvent('Invalid image', image);
|
AnalyticsService.logEvent('Invalid image', image);
|
||||||
@ -100,9 +100,8 @@ module.exports = function(
|
|||||||
image.logo = Boolean(image.logo);
|
image.logo = Boolean(image.logo);
|
||||||
image.bmap = Boolean(image.bmap);
|
image.bmap = Boolean(image.bmap);
|
||||||
|
|
||||||
AnalyticsService.logEvent('Select image', image);
|
return AnalyticsService.logEvent('Select image', image);
|
||||||
}).catch(ErrorService.reportException);
|
}).catch(ErrorService.reportException);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
module.exports = function(WarningModalService, SettingsModel) {
|
module.exports = function(WarningModalService, SettingsModel, ErrorService) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Client platform
|
* @summary Client platform
|
||||||
@ -78,12 +78,12 @@ module.exports = function(WarningModalService, SettingsModel) {
|
|||||||
// Keep the checkbox unchecked until the user confirms
|
// Keep the checkbox unchecked until the user confirms
|
||||||
this.currentData[name] = false;
|
this.currentData[name] = false;
|
||||||
|
|
||||||
WarningModalService.display(options).then((userAccepted) => {
|
return WarningModalService.display(options).then((userAccepted) => {
|
||||||
if (userAccepted) {
|
if (userAccepted) {
|
||||||
this.model.set(name, true);
|
this.model.set(name, true);
|
||||||
this.refreshSettings();
|
this.refreshSettings();
|
||||||
}
|
}
|
||||||
});
|
}).catch(ErrorService.reportException);
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,8 @@ 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'),
|
||||||
require('../../models/settings')
|
require('../../models/settings'),
|
||||||
|
require('../../modules/error')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
SettingsPage.controller('SettingsController', require('./controllers/settings'));
|
SettingsPage.controller('SettingsController', require('./controllers/settings'));
|
||||||
|
@ -39,7 +39,7 @@ module.exports = (ManifestBindService) => {
|
|||||||
const value = ManifestBindService.get(attributes.manifestBind);
|
const value = ManifestBindService.get(attributes.manifestBind);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new Error('ManifestBind: Unknown property `' + attributes.manifestBind + '`');
|
throw new Error(`ManifestBind: Unknown property \`${attributes.manifestBind}\``);
|
||||||
}
|
}
|
||||||
|
|
||||||
element.html(value);
|
element.html(value);
|
||||||
|
@ -33,7 +33,7 @@ module.exports = () => {
|
|||||||
*/
|
*/
|
||||||
return (input) => {
|
return (input) => {
|
||||||
if (!input) {
|
if (!input) {
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.basename(input);
|
return path.basename(input);
|
||||||
|
@ -48,9 +48,11 @@ exports.getEntries = (archive) => {
|
|||||||
zip.on('error', reject);
|
zip.on('error', reject);
|
||||||
|
|
||||||
zip.on('ready', () => {
|
zip.on('ready', () => {
|
||||||
|
const EMPTY_ENTRY_SIZE = 0;
|
||||||
|
|
||||||
return resolve(_.chain(zip.entries())
|
return resolve(_.chain(zip.entries())
|
||||||
.omitBy((entry) => {
|
.omitBy((entry) => {
|
||||||
return entry.size === 0;
|
return entry.size === EMPTY_ENTRY_SIZE;
|
||||||
})
|
})
|
||||||
.map((metadata) => {
|
.map((metadata) => {
|
||||||
return {
|
return {
|
||||||
@ -99,7 +101,7 @@ exports.extractFile = (archive, entries, file) => {
|
|||||||
return zipfile.readEntry();
|
return zipfile.readEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
zipfile.openReadStream(entry, (error, readStream) => {
|
return zipfile.openReadStream(entry, (error, readStream) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error);
|
return reject(error);
|
||||||
}
|
}
|
||||||
|
@ -170,11 +170,12 @@ 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) => {
|
||||||
const extension = path.extname(entry.name).slice(1);
|
const extension = _.replace(path.extname(entry.name), '.', '').toLowerCase();
|
||||||
return _.includes(IMAGE_EXTENSIONS, extension);
|
return _.includes(IMAGE_EXTENSIONS, extension);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (imageEntries.length !== 1) {
|
const VALID_NUMBER_OF_IMAGE_ENTRIES = 1;
|
||||||
|
if (imageEntries.length !== VALID_NUMBER_OF_IMAGE_ENTRIES) {
|
||||||
const error = new Error('Invalid archive image');
|
const error = new Error('Invalid archive image');
|
||||||
error.description = 'The archive image should contain one and only one top image file.';
|
error.description = 'The archive image should contain one and only one top image file.';
|
||||||
error.report = false;
|
error.report = false;
|
||||||
|
@ -33,9 +33,11 @@ const archiveType = require('archive-type');
|
|||||||
*/
|
*/
|
||||||
exports.getArchiveMimeType = (file) => {
|
exports.getArchiveMimeType = (file) => {
|
||||||
|
|
||||||
// archive-type only needs the first 261 bytes
|
// `archive-type` only needs the first 261 bytes
|
||||||
// See https://github.com/kevva/archive-type
|
// See https://github.com/kevva/archive-type
|
||||||
const chunk = readChunk.sync(file, 0, 261);
|
const MAGIC_NUMBER_BUFFER_START = 0;
|
||||||
|
const MAGIC_NUMBER_BUFFER_END = 261;
|
||||||
|
const chunk = readChunk.sync(file, MAGIC_NUMBER_BUFFER_START, MAGIC_NUMBER_BUFFER_END);
|
||||||
|
|
||||||
return _.get(archiveType(chunk), 'mime', 'application/octet-stream');
|
return _.get(archiveType(chunk), 'mime', 'application/octet-stream');
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,14 @@
|
|||||||
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
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
const UNKNOWN_SIZE = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Check if a drive is locked
|
* @summary Check if a drive is locked
|
||||||
* @function
|
* @function
|
||||||
@ -134,7 +142,7 @@ exports.isSourceDrive = (drive, image) => {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
exports.isDriveLargeEnough = (drive, image) => {
|
exports.isDriveLargeEnough = (drive, image) => {
|
||||||
return _.get(drive, 'size', 0) >= _.get(image, 'size', 0);
|
return _.get(drive, 'size', UNKNOWN_SIZE) >= _.get(image, 'size', UNKNOWN_SIZE);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,5 +206,5 @@ exports.isDriveValid = (drive, image) => {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
exports.isDriveSizeRecommended = (drive, image) => {
|
exports.isDriveSizeRecommended = (drive, image) => {
|
||||||
return _.get(drive, 'size', 0) >= _.get(image, 'recommendedDriveSize', 0);
|
return _.get(drive, 'size', UNKNOWN_SIZE) >= _.get(image, 'recommendedDriveSize', UNKNOWN_SIZE);
|
||||||
};
|
};
|
||||||
|
@ -60,7 +60,7 @@ exports.buildMessage = (title, data = {}) => {
|
|||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
command: title,
|
command: title,
|
||||||
data: data
|
data
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ exports.buildMessage = (title, data = {}) => {
|
|||||||
* > }
|
* > }
|
||||||
*/
|
*/
|
||||||
exports.parseMessage = (string) => {
|
exports.parseMessage = (string) => {
|
||||||
let output;
|
let output = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
output = JSON.parse(string);
|
output = JSON.parse(string);
|
||||||
@ -124,15 +124,13 @@ exports.parseMessage = (string) => {
|
|||||||
* > true
|
* > true
|
||||||
*/
|
*/
|
||||||
exports.buildErrorMessage = (error) => {
|
exports.buildErrorMessage = (error) => {
|
||||||
if (_.isString(error)) {
|
const errorObject = _.isString(error) ? new Error(error) : error;
|
||||||
error = new Error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return exports.buildMessage('error', {
|
return exports.buildMessage('error', {
|
||||||
message: error.message,
|
message: errorObject.message,
|
||||||
description: error.description,
|
description: errorObject.description,
|
||||||
stacktrace: error.stack,
|
stacktrace: errorObject.stack,
|
||||||
code: error.code
|
code: errorObject.code
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,39 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Gigabyte to byte ratio
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1 GB = 1e+9 B
|
||||||
|
*/
|
||||||
|
const GIGABYTE_TO_BYTE_RATIO = 1e+9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Megabyte to byte ratio
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1 MB = 1e+6 B
|
||||||
|
*/
|
||||||
|
const MEGABYTE_TO_BYTE_RATIO = 1e+6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Milliseconds in a day
|
||||||
|
* @constant
|
||||||
|
* @private
|
||||||
|
* @type {Number}
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* From 24 * 60 * 60 * 1000
|
||||||
|
*/
|
||||||
|
const MILLISECONDS_IN_A_DAY = 86400000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Convert bytes to gigabytes
|
* @summary Convert bytes to gigabytes
|
||||||
* @function
|
* @function
|
||||||
@ -28,7 +61,7 @@
|
|||||||
* const result = units.bytesToGigabytes(7801405440);
|
* const result = units.bytesToGigabytes(7801405440);
|
||||||
*/
|
*/
|
||||||
exports.bytesToGigabytes = (bytes) => {
|
exports.bytesToGigabytes = (bytes) => {
|
||||||
return bytes / 1e+9;
|
return bytes / GIGABYTE_TO_BYTE_RATIO;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,5 +76,20 @@ exports.bytesToGigabytes = (bytes) => {
|
|||||||
* const result = units.bytesToMegabytes(7801405440);
|
* const result = units.bytesToMegabytes(7801405440);
|
||||||
*/
|
*/
|
||||||
exports.bytesToMegabytes = (bytes) => {
|
exports.bytesToMegabytes = (bytes) => {
|
||||||
return bytes / 1e+6;
|
return bytes / MEGABYTE_TO_BYTE_RATIO;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Convert days to milliseconds
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*
|
||||||
|
* @param {Number} days - days
|
||||||
|
* @returns {Number} milliseconds
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const result = units.daysToMilliseconds(2);
|
||||||
|
*/
|
||||||
|
exports.daysToMilliseconds = (days) => {
|
||||||
|
return days * MILLISECONDS_IN_A_DAY;
|
||||||
};
|
};
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
"electron-mocha": "^3.1.1",
|
"electron-mocha": "^3.1.1",
|
||||||
"electron-packager": "^7.0.1",
|
"electron-packager": "^7.0.1",
|
||||||
"electron-prebuilt": "1.4.4",
|
"electron-prebuilt": "1.4.4",
|
||||||
"eslint": "^2.13.1",
|
"eslint": "^3.16.1",
|
||||||
"file-exists": "^1.0.0",
|
"file-exists": "^1.0.0",
|
||||||
"html-angular-validate": "^0.1.9",
|
"html-angular-validate": "^0.1.9",
|
||||||
"jsonfile": "^2.3.1",
|
"jsonfile": "^2.3.1",
|
||||||
|
@ -21,6 +21,7 @@ const jsonfile = require('jsonfile');
|
|||||||
const childProcess = require('child_process');
|
const childProcess = require('child_process');
|
||||||
const packageJSON = require('../package.json');
|
const packageJSON = require('../package.json');
|
||||||
const shrinkwrapIgnore = _.union(packageJSON.shrinkwrapIgnore, _.keys(packageJSON.optionalDependencies));
|
const shrinkwrapIgnore = _.union(packageJSON.shrinkwrapIgnore, _.keys(packageJSON.optionalDependencies));
|
||||||
|
const EXIT_CODES = require('../lib/shared/exit-codes');
|
||||||
const SHRINKWRAP_PATH = path.join(__dirname, '..', 'npm-shrinkwrap.json');
|
const SHRINKWRAP_PATH = path.join(__dirname, '..', 'npm-shrinkwrap.json');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -29,7 +30,7 @@ try {
|
|||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.stderr.toString());
|
console.error(error.stderr.toString());
|
||||||
process.exit(1);
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shrinkwrapContents = jsonfile.readFileSync(SHRINKWRAP_PATH);
|
const shrinkwrapContents = jsonfile.readFileSync(SHRINKWRAP_PATH);
|
||||||
|
@ -14,6 +14,7 @@ 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 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);
|
||||||
|
|
||||||
@ -45,16 +46,14 @@ angularValidate.validate(
|
|||||||
reportCheckstylePath: null
|
reportCheckstylePath: null
|
||||||
}
|
}
|
||||||
).then((result) => {
|
).then((result) => {
|
||||||
|
|
||||||
// console.log(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) => {
|
||||||
console.error(' ' + chalk.yellow(`[${error.line}:${error.col}]`) + ` ${error.msg}`);
|
const errorPosition = `[${error.line}:${error.col}]`;
|
||||||
|
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}`));
|
||||||
@ -71,16 +70,17 @@ angularValidate.validate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!result.allpassed) {
|
if (!result.allpassed) {
|
||||||
|
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(1);
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
||||||
}, 500);
|
}, EXIT_TIMEOUT_MS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
||||||
});
|
});
|
||||||
|
@ -22,19 +22,19 @@ console.log(_.flatten([
|
|||||||
packageJSON.packageIgnore,
|
packageJSON.packageIgnore,
|
||||||
|
|
||||||
// Development dependencies
|
// Development dependencies
|
||||||
_.map(_.keys(packageJSON.devDependencies), function(dependency) {
|
_.map(_.keys(packageJSON.devDependencies), (dependency) => {
|
||||||
return path.join('node_modules', dependency);
|
return path.join('node_modules', dependency);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Top level hidden files
|
// Top level hidden files
|
||||||
_.map(_.filter(topLevelFiles, function(file) {
|
_.map(_.filter(topLevelFiles, (file) => {
|
||||||
return _.startsWith(file, '.');
|
return _.startsWith(file, '.');
|
||||||
}), function(file) {
|
}), (file) => {
|
||||||
return '\\' + file;
|
return `\\${file}`;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Top level markdown files
|
// Top level markdown files
|
||||||
_.filter(topLevelFiles, function(file) {
|
_.filter(topLevelFiles, (file) => {
|
||||||
return _.endsWith(file, '.md');
|
return _.endsWith(file, '.md');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
19
tests/.eslintrc.yml
Normal file
19
tests/.eslintrc.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
rules:
|
||||||
|
require-jsdoc:
|
||||||
|
- off
|
||||||
|
no-undefined:
|
||||||
|
- off
|
||||||
|
init-declarations:
|
||||||
|
- off
|
||||||
|
no-unused-expressions:
|
||||||
|
- off
|
||||||
|
prefer-arrow-callback:
|
||||||
|
- off
|
||||||
|
no-magic-numbers:
|
||||||
|
- off
|
||||||
|
id-length:
|
||||||
|
- error
|
||||||
|
- min: 2
|
||||||
|
exceptions:
|
||||||
|
- "_"
|
||||||
|
- "m"
|
@ -39,7 +39,7 @@ describe('Browser: SVGIcon', function() {
|
|||||||
|
|
||||||
// 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);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const m = require('mochainon');
|
const m = require('mochainon');
|
||||||
const angular = require('angular');
|
const angular = require('angular');
|
||||||
|
const units = require('../../../lib/shared/units');
|
||||||
require('angular-mocks');
|
require('angular-mocks');
|
||||||
|
|
||||||
describe('Browser: UpdateNotifier', function() {
|
describe('Browser: UpdateNotifier', function() {
|
||||||
@ -16,12 +17,12 @@ describe('Browser: UpdateNotifier', function() {
|
|||||||
|
|
||||||
let UpdateNotifierService;
|
let UpdateNotifierService;
|
||||||
let SettingsModel;
|
let SettingsModel;
|
||||||
let UPDATE_NOTIFIER_SLEEP_TIME;
|
let UPDATE_NOTIFIER_SLEEP_DAYS;
|
||||||
|
|
||||||
beforeEach(angular.mock.inject(function(_UpdateNotifierService_, _SettingsModel_, _UPDATE_NOTIFIER_SLEEP_TIME_) {
|
beforeEach(angular.mock.inject(function(_UpdateNotifierService_, _SettingsModel_, _UPDATE_NOTIFIER_SLEEP_DAYS_) {
|
||||||
UpdateNotifierService = _UpdateNotifierService_;
|
UpdateNotifierService = _UpdateNotifierService_;
|
||||||
SettingsModel = _SettingsModel_;
|
SettingsModel = _SettingsModel_;
|
||||||
UPDATE_NOTIFIER_SLEEP_TIME = _UPDATE_NOTIFIER_SLEEP_TIME_;
|
UPDATE_NOTIFIER_SLEEP_DAYS = _UPDATE_NOTIFIER_SLEEP_DAYS_;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('given the `sleepUpdateCheck` is disabled', function() {
|
describe('given the `sleepUpdateCheck` is disabled', function() {
|
||||||
@ -72,7 +73,8 @@ describe('Browser: UpdateNotifier', function() {
|
|||||||
describe('given the `lastUpdateNotify` was updated long ago', function() {
|
describe('given the `lastUpdateNotify` was updated long ago', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
SettingsModel.set('lastUpdateNotify', Date.now() + UPDATE_NOTIFIER_SLEEP_TIME + 1000);
|
const SLEEP_MS = units.daysToMilliseconds(UPDATE_NOTIFIER_SLEEP_DAYS);
|
||||||
|
SettingsModel.set('lastUpdateNotify', Date.now() + SLEEP_MS + 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', function() {
|
it('should return true', function() {
|
||||||
|
@ -65,7 +65,7 @@ describe('Browser: SettingsModel', function() {
|
|||||||
const keyUnderTest = _.first(SUPPORTED_KEYS);
|
const keyUnderTest = _.first(SUPPORTED_KEYS);
|
||||||
m.chai.expect(function() {
|
m.chai.expect(function() {
|
||||||
SettingsModel.set(keyUnderTest, {
|
SettingsModel.set(keyUnderTest, {
|
||||||
x: 1
|
setting: 1
|
||||||
});
|
});
|
||||||
}).to.throw('Invalid setting value: [object Object]');
|
}).to.throw('Invalid setting value: [object Object]');
|
||||||
});
|
});
|
||||||
|
@ -73,35 +73,35 @@ describe('Browser: SupportedFormats', function() {
|
|||||||
|
|
||||||
it('should return true if the extension is included in .getAllExtensions()', function() {
|
it('should return true if the extension is included in .getAllExtensions()', function() {
|
||||||
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
||||||
const imagePath = '/path/to/foo.' + nonCompressedExtension;
|
const imagePath = `/path/to/foo.${nonCompressedExtension}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.true;
|
m.chai.expect(isSupported).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore casing when determining extension validity', function() {
|
it('should ignore casing when determining extension validity', function() {
|
||||||
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
||||||
const imagePath = '/path/to/foo.' + nonCompressedExtension.toUpperCase();
|
const imagePath = `/path/to/foo.${nonCompressedExtension.toUpperCase()}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.true;
|
m.chai.expect(isSupported).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider an extension before a non compressed extension', function() {
|
it('should not consider an extension before a non compressed extension', function() {
|
||||||
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
||||||
const imagePath = '/path/to/foo.1234.' + nonCompressedExtension;
|
const imagePath = `/path/to/foo.1234.${nonCompressedExtension}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.true;
|
m.chai.expect(isSupported).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if the extension is supported and the file name includes dots', function() {
|
it('should return true if the extension is supported and the file name includes dots', function() {
|
||||||
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
||||||
const imagePath = '/path/to/foo.1.2.3-bar.' + nonCompressedExtension;
|
const imagePath = `/path/to/foo.1.2.3-bar.${nonCompressedExtension}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.true;
|
m.chai.expect(isSupported).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if the extension is only a supported archive extension', function() {
|
it('should return true if the extension is only a supported archive extension', function() {
|
||||||
const archiveExtension = _.first(SupportedFormatsModel.getArchiveExtensions());
|
const archiveExtension = _.first(SupportedFormatsModel.getArchiveExtensions());
|
||||||
const imagePath = '/path/to/foo.' + archiveExtension;
|
const imagePath = `/path/to/foo.${archiveExtension}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.true;
|
m.chai.expect(isSupported).to.be.true;
|
||||||
});
|
});
|
||||||
@ -109,14 +109,14 @@ describe('Browser: SupportedFormats', function() {
|
|||||||
it('should return true if the extension is a supported one plus a supported compressed extensions', function() {
|
it('should return true if the extension is a supported one plus a supported compressed extensions', function() {
|
||||||
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
const nonCompressedExtension = _.first(SupportedFormatsModel.getNonCompressedExtensions());
|
||||||
const compressedExtension = _.first(SupportedFormatsModel.getCompressedExtensions());
|
const compressedExtension = _.first(SupportedFormatsModel.getCompressedExtensions());
|
||||||
const imagePath = '/path/to/foo.' + nonCompressedExtension + '.' + compressedExtension;
|
const imagePath = `/path/to/foo.${nonCompressedExtension}.${compressedExtension}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.true;
|
m.chai.expect(isSupported).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if the extension is an unsupported one plus a supported compressed extensions', function() {
|
it('should return false if the extension is an unsupported one plus a supported compressed extensions', function() {
|
||||||
const compressedExtension = _.first(SupportedFormatsModel.getCompressedExtensions());
|
const compressedExtension = _.first(SupportedFormatsModel.getCompressedExtensions());
|
||||||
const imagePath = '/path/to/foo.jpg.' + compressedExtension;
|
const imagePath = `/path/to/foo.jpg.${compressedExtension}`;
|
||||||
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
const isSupported = SupportedFormatsModel.isSupportedImage(imagePath);
|
||||||
m.chai.expect(isSupported).to.be.false;
|
m.chai.expect(isSupported).to.be.false;
|
||||||
});
|
});
|
||||||
|
@ -19,8 +19,8 @@ describe('Browser: Path', function() {
|
|||||||
basenameFilter = _basenameFilter_;
|
basenameFilter = _basenameFilter_;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should return undefined if no input', function() {
|
it('should return an empty string if no input', function() {
|
||||||
m.chai.expect(basenameFilter()).to.be.undefined;
|
m.chai.expect(basenameFilter()).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the basename', function() {
|
it('should return the basename', function() {
|
||||||
|
@ -39,6 +39,8 @@ const deleteIfExists = (file) => {
|
|||||||
if (fileExists(file)) {
|
if (fileExists(file)) {
|
||||||
return fs.unlinkAsync(file);
|
return fs.unlinkAsync(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Bluebird.resolve();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,7 +65,7 @@ exports.extractFromFilePath = function(file, image) {
|
|||||||
results.size.original === fs.statSync(file).size,
|
results.size.original === fs.statSync(file).size,
|
||||||
results.size.original === fs.statSync(image).size
|
results.size.original === fs.statSync(image).size
|
||||||
])) {
|
])) {
|
||||||
throw new Error('Invalid size: ' + results.size.original);
|
throw new Error(`Invalid size: ${results.size.original}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = results.stream
|
const stream = results.stream
|
||||||
|
Loading…
x
Reference in New Issue
Block a user