Convert utils, settings and errors to typescript

Change-type: patch
Changelog-entry: Convert utils, settings and errors to typescript
This commit is contained in:
Alexis Svinartchouk 2019-06-20 17:40:53 +02:00
parent 7be07bfe8c
commit 3706770322
40 changed files with 782 additions and 841 deletions

View File

@ -174,7 +174,7 @@ lint-spell:
lint: lint-ts lint-js lint-sass lint-cpp lint-html lint-spell
MOCHA_OPTIONS=--recursive --reporter spec
MOCHA_OPTIONS=--recursive --reporter spec --require ts-node/register
# See https://github.com/electron/spectron/issues/127
ETCHER_SPECTRON_ENTRYPOINT ?= $(shell node -e 'console.log(require("electron"))')

View File

@ -36,6 +36,7 @@ const messages = require('../../gui/app/modules/messages')
const store = require('./models/store')
const packageJSON = require('../../../package.json')
const flashState = require('./models/flash-state')
// eslint-disable-next-line node/no-missing-require
const settings = require('./models/settings')
const windowProgress = require('./os/window-progress')
const analytics = require('./modules/analytics')
@ -45,6 +46,7 @@ const driveScanner = require('./modules/drive-scanner')
const osDialog = require('./os/dialog')
const exceptionReporter = require('./modules/exception-reporter')
const updateLock = require('./modules/update-lock')
// eslint-disable-next-line node/no-missing-require
const screensaver = require('./modules/screensaver')
/* eslint-disable lodash/prefer-lodash-method,lodash/prefer-get */

View File

@ -24,6 +24,7 @@ const store = require('../../../models/store')
const analytics = require('../../../modules/analytics')
const availableDrives = require('../../../models/available-drives')
const selectionState = require('../../../models/selection-state')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../../../../gui/app/modules/utils')
module.exports = function (

View File

@ -18,7 +18,9 @@
const _ = require('lodash')
const os = require('os')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../../../../gui/app/modules/utils')
const angular = require('angular')

View File

@ -14,18 +14,21 @@
* limitations under the License.
*/
'use strict'
import { app, remote } from 'electron';
import { readFile, unlink, writeFile } from 'fs';
import { join } from 'path';
import { promisify } from 'util';
const Bluebird = require('bluebird')
const fs = require('fs')
const path = require('path')
const readFileAsync = promisify(readFile);
const writeFileAsync = promisify(writeFile);
const unlinkAsync = promisify(unlink);
/**
* @summary Number of spaces to indent JSON output with
* @type {Number}
* @constant
*/
const JSON_INDENT = 2
const JSON_INDENT = 2;
/**
* @summary Userdata directory path
@ -38,21 +41,16 @@ const JSON_INDENT = 2
* @constant
* @type {String}
*/
const USER_DATA_DIR = (() => {
// NOTE: The ternary is due to this module being loaded both,
// Electron's main process and renderer process
const electron = require('electron')
return electron.app
? electron.app.getPath('userData')
: electron.remote.app.getPath('userData')
})()
// NOTE: The ternary is due to this module being loaded both,
// Electron's main process and renderer process
const USER_DATA_DIR = (app || remote.app).getPath('userData');
/**
* @summary Configuration file path
* @type {String}
* @constant
*/
const CONFIG_PATH = path.join(USER_DATA_DIR, 'config.json')
const CONFIG_PATH = join(USER_DATA_DIR, 'config.json');
/**
* @summary Read a local config.json file
@ -68,26 +66,22 @@ const CONFIG_PATH = path.join(USER_DATA_DIR, 'config.json')
* console.log(settings)
* })
*/
const readConfigFile = (filename) => {
return new Bluebird((resolve, reject) => {
fs.readFile(filename, { encoding: 'utf8' }, (error, contents) => {
let data = {}
if (error) {
if (error.code === 'ENOENT') {
resolve(data)
} else {
reject(error)
}
} else {
try {
data = JSON.parse(contents)
} catch (parseError) {
console.error(parseError)
}
resolve(data)
}
})
})
async function readConfigFile(filename: string): Promise<any> {
let contents = '{}';
try {
contents = await readFileAsync(filename, { encoding: 'utf8' });
} catch (error) {
if (error.code === 'ENOENT') {
return {};
}
throw error;
}
try {
return JSON.parse(contents);
} catch (error) {
console.error(error);
return {};
}
}
/**
@ -106,17 +100,10 @@ const readConfigFile = (filename) => {
* console.log('data written')
* })
*/
const writeConfigFile = (filename, data) => {
return new Bluebird((resolve, reject) => {
const contents = JSON.stringify(data, null, JSON_INDENT)
fs.writeFile(filename, contents, (error) => {
if (error) {
reject(error)
} else {
resolve(data)
}
})
})
async function writeConfigFile(filename: string, data: any) {
const contents = JSON.stringify(data, null, JSON_INDENT);
await writeFileAsync(filename, contents);
return data;
}
/**
@ -132,8 +119,8 @@ const writeConfigFile = (filename, data) => {
* console.log(settings);
* });
*/
exports.readAll = () => {
return readConfigFile(CONFIG_PATH)
export async function readAll(): Promise<any> {
return await readConfigFile(CONFIG_PATH);
}
/**
@ -152,8 +139,8 @@ exports.readAll = () => {
* console.log('Done!');
* });
*/
exports.writeAll = (settings) => {
return writeConfigFile(CONFIG_PATH, settings)
export async function writeAll(settings: any) {
return await writeConfigFile(CONFIG_PATH, settings);
}
/**
@ -171,14 +158,6 @@ exports.writeAll = (settings) => {
* console.log('Done!');
* });
*/
exports.clear = () => {
return new Bluebird((resolve, reject) => {
fs.unlink(CONFIG_PATH, (error) => {
if (error) {
reject(error)
} else {
resolve()
}
})
})
export async function clear(): Promise<void> {
await unlinkAsync(CONFIG_PATH);
}

View File

@ -1,232 +0,0 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/**
* @module Etcher.Models.Settings
*/
const _ = require('lodash')
const Bluebird = require('bluebird')
const localSettings = require('./local-settings')
const errors = require('../modules/errors')
const packageJSON = require('../../../../package.json')
const debug = require('debug')('etcher:models:settings')
/**
* @summary Default settings
* @constant
* @type {Object}
*/
const DEFAULT_SETTINGS = {
unsafeMode: false,
errorReporting: true,
unmountOnSuccess: true,
validateWriteOnSuccess: true,
trim: false,
updatesEnabled: packageJSON.updates.enabled && !_.includes([ 'rpm', 'deb' ], packageJSON.packageType),
lastSleptUpdateNotifier: null,
lastSleptUpdateNotifierVersion: null,
desktopNotifications: true
}
/**
* @summary Settings state
* @type {Object}
* @private
*/
let settings = _.cloneDeep(DEFAULT_SETTINGS)
/**
* @summary Reset settings to their default values
* @function
* @public
*
* @returns {Promise}
*
* @example
* settings.reset().then(() => {
* console.log('Done!');
* });
*/
exports.reset = () => {
debug('reset')
// TODO: Remove default settings from config file (?)
settings = _.cloneDeep(DEFAULT_SETTINGS)
return localSettings.writeAll(settings)
}
/**
* @summary Extend the current settings
* @function
* @public
*
* @param {Object} value - value
* @returns {Promise}
*
* @example
* settings.assign({
* foo: 'bar'
* }).then(() => {
* console.log('Done!');
* });
*/
exports.assign = (value) => {
debug('assign', value)
if (_.isNil(value)) {
return Bluebird.reject(errors.createError({
title: 'Missing settings'
}))
}
if (!_.isPlainObject(value)) {
return Bluebird.reject(errors.createError({
title: 'Settings must be an object'
}))
}
const newSettings = _.assign({}, settings, value)
return localSettings.writeAll(newSettings)
.then((updatedSettings) => {
// NOTE: Only update in memory settings when successfully written
settings = updatedSettings
})
}
/**
* @summary Extend the application state with the local settings
* @function
* @public
*
* @returns {Promise}
*
* @example
* settings.load().then(() => {
* console.log('Done!');
* });
*/
exports.load = () => {
debug('load')
return localSettings.readAll().then((loadedSettings) => {
return _.assign(settings, loadedSettings)
})
}
/**
* @summary Set a setting value
* @function
* @public
*
* @param {String} key - setting key
* @param {*} value - setting value
* @returns {Promise}
*
* @example
* settings.set('unmountOnSuccess', true).then(() => {
* console.log('Done!');
* });
*/
exports.set = (key, value) => {
debug('set', key, value)
if (_.isNil(key)) {
return Bluebird.reject(errors.createError({
title: 'Missing setting key'
}))
}
if (!_.isString(key)) {
return Bluebird.reject(errors.createError({
title: `Invalid setting key: ${key}`
}))
}
const previousValue = settings[key]
settings[key] = value
return localSettings.writeAll(settings)
.catch((error) => {
// Revert to previous value if persisting settings failed
settings[key] = previousValue
throw error
})
}
/**
* @summary Get a setting value
* @function
* @public
*
* @param {String} key - setting key
* @returns {*} setting value
*
* @example
* const value = settings.get('unmountOnSuccess');
*/
exports.get = (key) => {
return _.cloneDeep(_.get(settings, [ key ]))
}
/**
* @summary Check if setting value exists
* @function
* @public
*
* @param {String} key - setting key
* @returns {Boolean} exists
*
* @example
* const hasValue = settings.has('unmountOnSuccess');
*/
exports.has = (key) => {
/* eslint-disable no-eq-null */
return settings[key] != null
}
/**
* @summary Get all setting values
* @function
* @public
*
* @returns {Object} all setting values
*
* @example
* const allSettings = settings.getAll();
* console.log(allSettings.unmountOnSuccess);
*/
exports.getAll = () => {
debug('getAll')
return _.cloneDeep(settings)
}
/**
* @summary Get the default setting values
* @function
* @public
*
* @returns {Object} all setting values
*
* @example
* const defaults = settings.getDefaults();
* console.log(defaults.unmountOnSuccess);
*/
exports.getDefaults = () => {
debug('getDefaults')
return _.cloneDeep(DEFAULT_SETTINGS)
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as debug_ from 'debug';
import { cloneDeep, isPlainObject } from 'lodash';
import { createError } from '../modules/errors';
import { Dict } from '../modules/utils';
import { readAll, writeAll } from './local-settings';
import * as packageJSON from '../../../../package.json';
const debug = debug_('etcher:models:settings');
const DEFAULT_SETTINGS = {
unsafeMode: false,
errorReporting: true,
unmountOnSuccess: true,
validateWriteOnSuccess: true,
trim: false,
updatesEnabled:
packageJSON.updates.enabled &&
!['rpm', 'deb'].includes(packageJSON.packageType),
lastSleptUpdateNotifier: null,
lastSleptUpdateNotifierVersion: null,
desktopNotifications: true,
};
let settings: Dict<any> = cloneDeep(DEFAULT_SETTINGS);
export async function reset(): Promise<void> {
debug('reset');
// TODO: Remove default settings from config file (?)
settings = cloneDeep(DEFAULT_SETTINGS);
await writeAll(settings);
}
export async function assign(value: any): Promise<void> {
debug('assign', value);
if (!isPlainObject(value)) {
throw createError({ title: 'Settings must be an object' });
}
const newSettings = { ...settings, ...value };
const updatedSettings = await writeAll(newSettings);
// NOTE: Only update in memory settings when successfully written
settings = updatedSettings;
}
export async function load(): Promise<any> {
debug('load');
const loadedSettings = await readAll();
settings = { ...settings, ...loadedSettings };
return settings;
}
export async function set(key: string, value: any): Promise<void> {
debug('set', key, value);
if (typeof key !== 'string') {
throw createError({ title: `Invalid setting key: ${key}` });
}
const previousValue = settings[key];
settings[key] = value;
try {
await writeAll(settings);
} catch (error) {
// Revert to previous value if persisting settings failed
settings[key] = previousValue;
throw error;
}
}
export function get(key: string): any {
return cloneDeep(settings[key]);
}
export function has(key: string): boolean {
return settings[key] !== undefined;
}
export function getAll(): any {
debug('getAll');
return cloneDeep(settings);
}
export function getDefaults(): any {
debug('getDefaults');
return cloneDeep(DEFAULT_SETTINGS);
}

View File

@ -22,9 +22,12 @@ const redux = require('redux')
const uuidV4 = require('uuid/v4')
const constraints = require('../modules/drive-constraints')
const supportedFormats = require('../modules/supported-formats')
// eslint-disable-next-line node/no-missing-require
const errors = require('../modules/errors')
const fileExtensions = require('../modules/file-extensions')
// eslint-disable-next-line node/no-missing-require
const utils = require('../modules/utils')
// eslint-disable-next-line node/no-missing-require
const settings = require('./settings')
/**

View File

@ -19,7 +19,9 @@
const _ = require('lodash')
const resinCorvus = require('resin-corvus/browser')
const packageJSON = require('../../../../package.json')
// eslint-disable-next-line node/no-missing-require
const settings = require('../models/settings')
// eslint-disable-next-line node/no-missing-require
const { getConfig, hasProps } = require('../../../gui/app/modules/utils')
const sentryToken = settings.get('analyticsSentryToken') ||

View File

@ -21,6 +21,7 @@ const _ = require('lodash')
const ipc = require('node-ipc')
const sdk = require('etcher-sdk')
const EXIT_CODES = require('./exit-codes')
// eslint-disable-next-line node/no-missing-require
const errors = require('./errors')
ipc.config.id = process.env.IPC_CLIENT_ID

View File

@ -19,6 +19,7 @@
const sdk = require('etcher-sdk')
const process = require('process')
// eslint-disable-next-line node/no-missing-require
const settings = require('../models/settings')
/**

View File

@ -1,369 +0,0 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
const _ = require('lodash')
/**
* @summary Create an error details object
* @function
* @private
*
* @param {Object} options - options
* @param {(String|Function)} options.title - error title
* @param {(String|Function)} options.description - error description
* @returns {Object} error details object
*
* @example
* const details = createErrorDetails({
* title: (error) => {
* return `An error happened, the code is ${error.code}`;
* },
* description: 'This is the error description'
* });
*/
const createErrorDetails = (options) => {
return _.pick(_.mapValues(options, (value) => {
return _.isFunction(value) ? value : _.constant(value)
}), [ 'title', 'description' ])
}
/**
* @summary Human-friendly error messages
* @namespace HUMAN_FRIENDLY
* @public
*/
exports.HUMAN_FRIENDLY = {
/* eslint-disable new-cap */
/**
* @namespace ENOENT
* @memberof HUMAN_FRIENDLY
*/
ENOENT: createErrorDetails({
title: (error) => {
return `No such file or directory: ${error.path}`
},
description: 'The file you\'re trying to access doesn\'t exist'
}),
/**
* @namespace EPERM
* @memberof HUMAN_FRIENDLY
*/
EPERM: createErrorDetails({
title: 'You\'re not authorized to perform this operation',
description: 'Please ensure you have necessary permissions for this task'
}),
/**
* @namespace EACCES
* @memberof HUMAN_FRIENDLY
*/
EACCES: createErrorDetails({
title: 'You don\'t have access to this resource',
description: 'Please ensure you have necessary permissions to access this resource'
}),
/**
* @namespace ENOMEM
* @memberof HUMAN_FRIENDLY
*/
ENOMEM: createErrorDetails({
title: 'Your system ran out of memory',
description: 'Please make sure your system has enough available memory for this task'
})
/* eslint-enable new-cap */
}
/**
* @summary Get user friendly property from an error
* @function
* @private
*
* @param {Error} error - error
* @param {String} property - HUMAN_FRIENDLY property
* @returns {(String|Undefined)} user friendly message
*
* @example
* const error = new Error('My error');
* error.code = 'ENOMEM';
*
* const friendlyDescription = getUserFriendlyMessageProperty(error, 'description');
*
* if (friendlyDescription) {
* console.log(friendlyDescription);
* }
*/
const getUserFriendlyMessageProperty = (error, property) => {
const code = _.get(error, [ 'code' ])
if (_.isNil(code) || !_.isString(code)) {
return null
}
return _.invoke(exports.HUMAN_FRIENDLY, [ code, property ], error)
}
/**
* @summary Check if a string is blank
* @function
* @private
*
* @param {String} string - string
* @returns {Boolean} whether the string is blank
*
* @example
* if (isBlank(' ')) {
* console.log('The string is blank');
* }
*/
const isBlank = _.flow([ _.trim, _.isEmpty ])
/**
* @summary Get the title of an error
* @function
* @public
*
* @description
* Try to get as much information as possible about the error
* rather than falling back to generic messages right away.
*
* @param {Error} error - error
* @returns {String} error title
*
* @example
* const error = new Error('Foo bar');
* const title = errors.getTitle(error);
* console.log(title);
*/
exports.getTitle = (error) => {
if (!_.isError(error) && !_.isPlainObject(error) && !_.isNil(error)) {
return _.toString(error)
}
const codeTitle = getUserFriendlyMessageProperty(error, 'title')
if (!_.isNil(codeTitle)) {
return codeTitle
}
const message = _.get(error, [ 'message' ])
if (!isBlank(message)) {
return message
}
const code = _.get(error, [ 'code' ])
if (!_.isNil(code) && !isBlank(code)) {
return `Error code: ${code}`
}
return 'An error ocurred'
}
/**
* @summary Get the description of an error
* @function
* @public
*
* @param {Error} error - error
* @param {Object} options - options
* @param {Boolean} [options.userFriendlyDescriptionsOnly=false] - only return user friendly descriptions
* @returns {String} error description
*
* @example
* const error = new Error('Foo bar');
* const description = errors.getDescription(error);
* console.log(description);
*/
exports.getDescription = (error, options = {}) => {
_.defaults(options, {
userFriendlyDescriptionsOnly: false
})
if (!_.isError(error) && !_.isPlainObject(error)) {
return ''
}
if (!isBlank(error.description)) {
return error.description
}
const codeDescription = getUserFriendlyMessageProperty(error, 'description')
if (!_.isNil(codeDescription)) {
return codeDescription
}
if (options.userFriendlyDescriptionsOnly) {
return ''
}
if (error.stack) {
return error.stack
}
if (_.isEmpty(error)) {
return ''
}
const INDENTATION_SPACES = 2
return JSON.stringify(error, null, INDENTATION_SPACES)
}
/**
* @summary Create an error
* @function
* @public
*
* @param {Object} options - options
* @param {String} options.title - error title
* @param {String} [options.description] - error description
* @param {Boolean} [options.report] - report error
* @returns {Error} error
*
* @example
* const error = errors.createError({
* title: 'Foo'
* description: 'Bar'
* });
*
* throw error;
*/
exports.createError = (options) => {
if (isBlank(options.title)) {
throw new Error(`Invalid error title: ${options.title}`)
}
const error = new Error(options.title)
error.description = options.description
if (!_.isNil(options.report) && !options.report) {
error.report = false
}
if (!_.isNil(options.code)) {
error.code = options.code
}
return error
}
/**
* @summary Create a user error
* @function
* @public
*
* @description
* User errors represent invalid states that the user
* caused, that are not errors on the application itself.
* Therefore, user errors don't get reported to analytics
* and error reporting services.
*
* @param {Object} options - options
* @param {String} options.title - error title
* @param {String} [options.description] - error description
* @returns {Error} user error
*
* @example
* const error = errors.createUserError({
* title: 'Foo',
* description: 'Bar'
* });
*
* throw error;
*/
exports.createUserError = (options) => {
return exports.createError({
title: options.title,
description: options.description,
report: false,
code: options.code
})
}
/**
* @summary Check if an error is an user error
* @function
* @public
*
* @param {Error} error - error
* @returns {Boolean} whether the error is a user error
*
* @example
* const error = errors.createUserError('Foo', 'Bar');
*
* if (errors.isUserError(error)) {
* console.log('This error is a user error');
* }
*/
exports.isUserError = (error) => {
return _.isNil(error.report) ? false : !error.report
}
/**
* @summary Convert an Error object to a JSON object
* @function
* @public
*
* @param {Error} error - error object
* @returns {Object} json error
*
* @example
* const error = errors.toJSON(new Error('foo'))
*
* console.log(error.message);
* > 'foo'
*/
exports.toJSON = (error) => {
// Handle string error objects to be on the safe side
const isErrorLike = _.isError(error) || _.isPlainObject(error)
const errorObject = isErrorLike ? error : new Error(error)
return {
name: errorObject.name,
message: errorObject.message,
description: errorObject.description,
stack: errorObject.stack,
report: errorObject.report,
code: errorObject.code,
syscall: errorObject.syscall,
errno: errorObject.errno,
stdout: errorObject.stdout,
stderr: errorObject.stderr,
device: errorObject.device
}
}
/**
* @summary Convert a JSON object to an Error object
* @function
* @public
*
* @param {Error} json - json object
* @returns {Object} error object
*
* @example
* const error = errors.fromJSON(errors.toJSON(new Error('foo')));
*
* console.log(error.message);
* > 'foo'
*/
exports.fromJSON = (json) => {
return _.assign(new Error(json.message), json)
}

View File

@ -0,0 +1,347 @@
/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
assign,
flow,
invoke,
isEmpty,
isError,
isNil,
isPlainObject,
isString,
toString,
trim,
} from 'lodash';
import { Dict } from './utils';
const INDENTATION_SPACES = 2;
/**
* @summary Human-friendly error messages
*/
export const HUMAN_FRIENDLY: Dict<{
title: (error?: { path?: string }) => string;
description: (error?: any) => string;
}> = {
ENOENT: {
title: (error: { path: string }) =>
`No such file or directory: ${error.path}`,
description: () => "The file you're trying to access doesn't exist",
},
EPERM: {
title: () => "You're not authorized to perform this operation",
description: () =>
'Please ensure you have necessary permissions for this task',
},
EACCES: {
title: () => "You don't have access to this resource",
description: () =>
'Please ensure you have necessary permissions to access this resource',
},
ENOMEM: {
title: () => 'Your system ran out of memory',
description: () =>
'Please make sure your system has enough available memory for this task',
},
};
/**
* @summary Get user friendly property from an error
* @function
* @private
*
* @param {Error} error - error
* @param {String} property - HUMAN_FRIENDLY property
* @returns {(String|Undefined)} user friendly message
*
* @example
* const error = new Error('My error');
* error.code = 'ENOMEM';
*
* const friendlyDescription = getUserFriendlyMessageProperty(error, 'description');
*
* if (friendlyDescription) {
* console.log(friendlyDescription);
* }
*/
function getUserFriendlyMessageProperty(
error: { code?: string; path?: string },
property: 'title' | 'description',
): string | null {
const code = error.code;
if (!isString(code)) {
return null;
}
return invoke(HUMAN_FRIENDLY, [code, property], error);
}
/**
* @summary Check if a string is blank
* @function
* @private
*
* @param {String} string - string
* @returns {Boolean} whether the string is blank
*
* @example
* if (isBlank(' ')) {
* console.log('The string is blank');
* }
*/
const isBlank = flow([trim, isEmpty]);
/**
* @summary Get the title of an error
* @function
* @public
*
* @description
* Try to get as much information as possible about the error
* rather than falling back to generic messages right away.
*
* @param {Error} error - error
* @returns {String} error title
*
* @example
* const error = new Error('Foo bar');
* const title = errors.getTitle(error);
* console.log(title);
*/
export function getTitle(error: Error | Dict<any>): string {
if (!isError(error) && !isPlainObject(error) && !isNil(error)) {
return toString(error);
}
const codeTitle = getUserFriendlyMessageProperty(error, 'title');
if (!isNil(codeTitle)) {
return codeTitle;
}
const message = error.message;
if (!isBlank(message)) {
return message;
}
const code = error.code;
if (!isNil(code) && !isBlank(code)) {
return `Error code: ${code}`;
}
return 'An error ocurred';
}
/**
* @summary Get the description of an error
* @function
* @public
*
* @param {Error} error - error
* @returns {String} error description
*
* @example
* const error = new Error('Foo bar');
* const description = errors.getDescription(error);
* console.log(description);
*/
export function getDescription(error: {
code?: string;
description?: string;
stack?: string;
}): string {
if (!isError(error) && !isPlainObject(error)) {
return '';
}
if (!isBlank(error.description)) {
return error.description as string;
}
const codeDescription = getUserFriendlyMessageProperty(error, 'description');
if (!isNil(codeDescription)) {
return codeDescription;
}
if (error.stack) {
return error.stack;
}
if (isEmpty(error)) {
return '';
}
return JSON.stringify(error, null, INDENTATION_SPACES);
}
/**
* @summary Create an error
* @function
* @public
*
* @param {Object} options - options
* @param {String} options.title - error title
* @param {String} [options.description] - error description
* @param {Boolean} [options.report] - report error
* @returns {Error} error
*
* @example
* const error = errors.createError({
* title: 'Foo'
* description: 'Bar'
* });
*
* throw error;
*/
export function createError(options: {
title: string;
description?: string;
report?: boolean;
code?: string;
}): Error & { description?: string; report?: boolean; code?: string } {
if (isBlank(options.title)) {
throw new Error(`Invalid error title: ${options.title}`);
}
const error: Error & {
description?: string;
report?: boolean;
code?: string;
} = new Error(options.title);
error.description = options.description;
if (!isNil(options.report) && !options.report) {
error.report = false;
}
if (!isNil(options.code)) {
error.code = options.code;
}
return error;
}
/**
* @summary Create a user error
* @function
* @public
*
* @description
* User errors represent invalid states that the user
* caused, that are not errors on the application itself.
* Therefore, user errors don't get reported to analytics
* and error reporting services.
*
* @returns {Error} user error
*
* @example
* const error = errors.createUserError({
* title: 'Foo',
* description: 'Bar'
* });
*
* throw error;
*/
export function createUserError(options: {
title: string;
description: string;
code?: string;
}): Error {
return createError({
title: options.title,
description: options.description,
report: false,
code: options.code,
});
}
/**
* @summary Check if an error is an user error
* @function
* @public
*
* @param {Error} error - error
* @returns {Boolean} whether the error is a user error
*
* @example
* const error = errors.createUserError('Foo', 'Bar');
*
* if (errors.isUserError(error)) {
* console.log('This error is a user error');
* }
*/
export function isUserError(error: { report?: boolean }): boolean {
return isNil(error.report) ? false : !error.report;
}
/**
* @summary Convert an Error object to a JSON object
* @function
* @public
*
* @param {Error} error - error object
* @returns {Object} json error
*
* @example
* const error = errors.toJSON(new Error('foo'))
*
* console.log(error.message);
* > 'foo'
*/
export function toJSON(
error: Error & {
description?: string;
report?: boolean;
code?: string;
syscall?: string;
errno?: string | number;
stdout?: string;
stderr?: string;
device?: any;
},
) {
return {
name: error.name,
message: error.message,
description: error.description,
stack: error.stack,
report: error.report,
code: error.code,
syscall: error.syscall,
errno: error.errno,
stdout: error.stdout,
stderr: error.stderr,
device: error.device,
};
}
/**
* @summary Convert a JSON object to an Error object
* @function
* @public
*
* @param {Error} json - json object
* @returns {Object} error object
*
* @example
* const error = errors.fromJSON(errors.toJSON(new Error('foo')));
*
* console.log(error.message);
* > 'foo'
*/
export function fromJSON(json: Dict<any>): Error {
return assign(new Error(json.message), json);
}

View File

@ -24,8 +24,10 @@ const ipc = require('node-ipc')
const isRunningInAsar = require('electron-is-running-in-asar')
const electron = require('electron')
const store = require('../models/store')
// eslint-disable-next-line node/no-missing-require
const settings = require('../models/settings')
const flashState = require('../models/flash-state')
// eslint-disable-next-line node/no-missing-require
const errors = require('../../../gui/app/modules/errors')
const permissions = require('../../../gui/app/modules/permissions')
const windowProgress = require('../os/window-progress')

View File

@ -26,8 +26,10 @@ const os = require('os')
const sudoPrompt = Bluebird.promisifyAll(require('sudo-prompt'))
const { promisify } = require('util')
// eslint-disable-next-line node/no-missing-require
const errors = require('./errors')
// eslint-disable-next-line node/no-missing-require
const { tmpFileDisposer } = require('./utils')
const writeFileAsync = promisify(fs.writeFile)

View File

@ -16,7 +16,9 @@
'use strict'
// eslint-disable-next-line node/no-missing-require
const settings = require('../models/settings')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../../gui/app/modules/utils')
const units = require('../../../gui/app/modules/units')

View File

@ -21,6 +21,7 @@ const EventEmitter = require('events')
const createInactivityTimer = require('inactivity-timer')
const debug = require('debug')('etcher:update-lock')
const analytics = require('./analytics')
// eslint-disable-next-line node/no-missing-require
const settings = require('../models/settings')
/* eslint-disable no-magic-numbers, callback-return */

View File

@ -14,14 +14,15 @@
* limitations under the License.
*/
'use strict'
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as request from 'request';
import * as tmp from 'tmp';
import { promisify } from 'util';
const _ = require('lodash')
const Bluebird = require('bluebird')
const request = Bluebird.promisifyAll(require('request'))
const tmp = require('tmp')
import * as errors from './errors';
const errors = require('./errors')
const getAsync = promisify(request.get);
/**
* @summary Minimum percentage value
@ -29,7 +30,7 @@ const errors = require('./errors')
* @public
* @type {Number}
*/
exports.PERCENTAGE_MINIMUM = 0
export const PERCENTAGE_MINIMUM = 0;
/**
* @summary Maximum percentage value
@ -37,7 +38,7 @@ exports.PERCENTAGE_MINIMUM = 0
* @public
* @type {Number}
*/
exports.PERCENTAGE_MAXIMUM = 100
export const PERCENTAGE_MAXIMUM = 100;
/**
* @summary Check if a percentage is valid
@ -52,12 +53,12 @@ exports.PERCENTAGE_MAXIMUM = 100
* console.log('The percentage is valid');
* }
*/
exports.isValidPercentage = (percentage) => {
return _.every([
_.isNumber(percentage),
percentage >= exports.PERCENTAGE_MINIMUM,
percentage <= exports.PERCENTAGE_MAXIMUM
])
export function isValidPercentage(percentage: number) {
return _.every([
_.isNumber(percentage),
percentage >= exports.PERCENTAGE_MINIMUM,
percentage <= exports.PERCENTAGE_MAXIMUM,
]);
}
/**
@ -73,14 +74,14 @@ exports.isValidPercentage = (percentage) => {
* console.log(value);
* > 0.5
*/
exports.percentageToFloat = (percentage) => {
if (!exports.isValidPercentage(percentage)) {
throw errors.createError({
title: `Invalid percentage: ${percentage}`
})
}
export function percentageToFloat(percentage: number) {
if (!isValidPercentage(percentage)) {
throw errors.createError({
title: `Invalid percentage: ${percentage}`,
});
}
return percentage / exports.PERCENTAGE_MAXIMUM
return percentage / PERCENTAGE_MAXIMUM;
}
/**
@ -109,37 +110,40 @@ exports.percentageToFloat = (percentage) => {
*
* const memoizedFunction = memoize(getList, angular.equals);
*/
exports.memoize = (func, comparer) => {
let previousTuples = []
export function memoize(
func: (...args: any[]) => any,
comparer: (a: any, b: any) => boolean,
) {
let previousTuples: any[] = [];
return (...restArgs) => {
let areArgsInTuple = false
let state = Reflect.apply(func, this, restArgs)
return (...restArgs: any[]) => {
let areArgsInTuple = false;
let state = Reflect.apply(func, this, restArgs);
previousTuples = _.map(previousTuples, ([ oldArgs, oldState ]) => {
if (comparer(oldArgs, restArgs)) {
areArgsInTuple = true
previousTuples = _.map(previousTuples, ([oldArgs, oldState]) => {
if (comparer(oldArgs, restArgs)) {
areArgsInTuple = true;
if (comparer(state, oldState)) {
// Use the previously memoized state for this argument
state = oldState
}
if (comparer(state, oldState)) {
// Use the previously memoized state for this argument
state = oldState;
}
// Update the tuple state
return [ oldArgs, state ]
}
// Update the tuple state
return [oldArgs, state];
}
// Return the tuple unchanged
return [ oldArgs, oldState ]
})
// Return the tuple unchanged
return [oldArgs, oldState];
});
// Add the state associated with these args to be memoized
if (!areArgsInTuple) {
previousTuples.push([ restArgs, state ])
}
// Add the state associated with these args to be memoized
if (!areArgsInTuple) {
previousTuples.push([restArgs, state]);
}
return state
}
return state;
};
}
/**
@ -155,20 +159,19 @@ exports.memoize = (func, comparer) => {
* @example
* const doesIt = hasProps({ foo: 'bar' }, [ 'foo' ]);
*/
exports.hasProps = (obj, props) => {
return _.every(props, (prop) => {
return _.has(obj, prop)
})
export function hasProps(obj: any, props: string[]) {
return _.every(props, prop => {
return _.has(obj, prop);
});
}
/**
* @summary Get etcher configs stored online
* @param {String} - url where config.json is stored
*/
// eslint-disable-next-line
exports.getConfig = (configUrl) => {
return request.getAsync(configUrl, { json: true })
.get('body')
* @summary Get etcher configs stored online
* @param {String} - url where config.json is stored
*/
export async function getConfig(configUrl: string) {
// @ts-ignore
return (await getAsync(configUrl, { json: true })).body;
}
/**
@ -186,16 +189,16 @@ exports.getConfig = (configUrl) => {
* cleanup()
* });
*/
const tmpFileAsync = (options) => {
return new Promise((resolve, reject) => {
tmp.file(options, (error, path, _fd, cleanup) => {
if (error) {
reject(error)
} else {
resolve({ path, cleanup })
}
})
})
function tmpFileAsync(options: tmp.FileOptions) {
return new Promise((resolve, reject) => {
tmp.file(options, (error, path, _fd, cleanup) => {
if (error) {
reject(error);
} else {
resolve({ path, cleanup });
}
});
});
}
/**
@ -211,9 +214,12 @@ const tmpFileAsync = (options) => {
* console.log(path);
* })
*/
exports.tmpFileDisposer = (options) => {
return Bluebird.resolve(tmpFileAsync(options))
.disposer(({ cleanup }) => {
cleanup()
})
export function tmpFileDisposer(options: tmp.FileOptions) {
return Bluebird.resolve(tmpFileAsync(options)).disposer(({ cleanup }) => {
cleanup();
});
}
export interface Dict<T> {
[key: string]: T;
}

View File

@ -19,6 +19,7 @@
const _ = require('lodash')
const electron = require('electron')
const Bluebird = require('bluebird')
// eslint-disable-next-line node/no-missing-require
const errors = require('../../../gui/app/modules/errors')
const supportedFormats = require('../../../gui/app/modules/supported-formats')

View File

@ -17,6 +17,7 @@
'use strict'
const electron = require('electron')
// eslint-disable-next-line node/no-missing-require
const settings = require('../models/settings')
/**

View File

@ -19,6 +19,7 @@
const electron = require('electron')
const store = require('../../../models/store')
const analytics = require('../../../modules/analytics')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
module.exports = function () {

View File

@ -17,6 +17,7 @@
'use strict'
const electron = require('electron')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../../gui/app/modules/utils')
const progressStatus = require('../modules/progress-status')

View File

@ -25,6 +25,7 @@ const Path = require('path')
const process = require('process')
const { promisify } = require('util')
// eslint-disable-next-line node/no-missing-require
const { tmpFileDisposer } = require('../../../gui/app/modules/utils')
const readFileAsync = promisify(fs.readFile)

View File

@ -19,6 +19,7 @@
const _ = require('lodash')
const uuidV4 = require('uuid/v4')
const store = require('../../../models/store')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
const flashState = require('../../../models/flash-state')
const selectionState = require('../../../models/selection-state')

View File

@ -20,10 +20,12 @@ const _ = require('lodash')
const angular = require('angular')
const prettyBytes = require('pretty-bytes')
const store = require('../../../models/store')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
const selectionState = require('../../../models/selection-state')
const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../../../../gui/app/modules/utils')
module.exports = function (DriveSelectorService) {

View File

@ -23,9 +23,11 @@ const sdk = require('etcher-sdk')
const store = require('../../../models/store')
const messages = require('../../../../../gui/app/modules/messages')
// eslint-disable-next-line node/no-missing-require
const errors = require('../../../../../gui/app/modules/errors')
const supportedFormats = require('../../../../../gui/app/modules/supported-formats')
const analytics = require('../../../modules/analytics')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
const selectionState = require('../../../models/selection-state')
const osDialog = require('../../../os/dialog')

View File

@ -18,6 +18,7 @@
const path = require('path')
const store = require('../../../models/store')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
const flashState = require('../../../models/flash-state')
const analytics = require('../../../modules/analytics')

View File

@ -19,6 +19,7 @@
const os = require('os')
const _ = require('lodash')
const store = require('../../../models/store')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../models/settings')
const analytics = require('../../../modules/analytics')
const exceptionReporter = require('../../../modules/exception-reporter')

View File

@ -16,6 +16,7 @@
'use strict'
// eslint-disable-next-line node/no-missing-require
const errors = require('../../../../../gui/app/modules/errors')
/**

View File

@ -23,8 +23,10 @@ const { autoUpdater } = require('electron-updater')
const Bluebird = require('bluebird')
const EXIT_CODES = require('./app/modules/exit-codes')
const buildWindowMenu = require('./menu')
// eslint-disable-next-line node/no-missing-require
const settings = require('./app/models/settings')
const analytics = require('./app/modules/analytics')
// eslint-disable-next-line node/no-missing-require
const { getConfig } = require('./app/modules/utils')
/* eslint-disable lodash/prefer-lodash-method */

View File

@ -27,7 +27,7 @@
// an older equivalent of `ELECTRON_RUN_AS_NODE` that still gets set when
// using `child_process.fork()`.
if (process.env.ELECTRON_RUN_AS_NODE || process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE) {
require('./gui/app/modules/child-writer')
require('../generated/child-writer')
} else {
require('../generated/etcher')
}

90
npm-shrinkwrap.json generated
View File

@ -980,6 +980,12 @@
"integrity": "sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ==",
"dev": true
},
"@types/caseless": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true
},
"@types/color": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/color/-/color-2.0.1.tgz",
@ -1001,6 +1007,12 @@
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
},
"@types/debug": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.4.tgz",
"integrity": "sha512-D9MyoQFI7iP5VdpEyPZyjjqIJ8Y8EDNQFIFVLOmeg1rI1xiHOChyUPMPRUVfqFCerxfE+yS3vMyj37F6IdtOoQ==",
"dev": true
},
"@types/depcheck": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/depcheck/-/depcheck-0.6.0.tgz",
@ -1013,6 +1025,15 @@
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
"dev": true
},
"@types/form-data": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
"integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/glob": {
"version": "5.0.36",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz",
@ -1127,6 +1148,18 @@
"@types/react": "*"
}
},
"@types/request": {
"version": "2.48.1",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz",
"integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==",
"dev": true,
"requires": {
"@types/caseless": "*",
"@types/form-data": "*",
"@types/node": "*",
"@types/tough-cookie": "*"
}
},
"@types/styled-components": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-4.1.8.tgz",
@ -1145,6 +1178,18 @@
"csstype": "^2.6.4"
}
},
"@types/tmp": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA==",
"dev": true
},
"@types/tough-cookie": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz",
"integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==",
"dev": true
},
"@types/usb": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.1.tgz",
@ -1674,6 +1719,12 @@
"readable-stream": "^2.0.6"
}
},
"arg": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz",
"integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==",
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -4504,9 +4555,9 @@
},
"dependencies": {
"commander": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
"dev": true
},
"fs-extra": {
@ -7823,6 +7874,12 @@
}
}
},
"make-error": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true
},
"mamacro": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz",
@ -12678,6 +12735,27 @@
}
}
},
"ts-node": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz",
"integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.6",
"yn": "^3.0.0"
},
"dependencies": {
"diff": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
"integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
"dev": true
}
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@ -14300,6 +14378,12 @@
"fd-slicer": "~1.1.0"
}
},
"yn": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz",
"integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==",
"dev": true
},
"zip-stream": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz",

View File

@ -86,8 +86,11 @@
"@babel/plugin-proposal-function-bind": "^7.2.0",
"@babel/preset-env": "^7.2.0",
"@babel/preset-react": "^7.0.0",
"@types/debug": "^4.1.4",
"@types/node": "^10.14.9",
"@types/react-dom": "^16.8.4",
"@types/request": "^2.48.1",
"@types/tmp": "^0.1.0",
"acorn": "^6.0.5",
"angular-mocks": "1.7.6",
"babel-loader": "^8.0.4",
@ -119,6 +122,7 @@
"spectron": "^5.0.0",
"style-loader": "^0.23.1",
"ts-loader": "^6.0.2",
"ts-node": "^8.3.0",
"typescript": "^3.5.1",
"webpack": "^4.31.0",
"webpack-cli": "^3.1.2",

View File

@ -20,6 +20,7 @@ const _ = require('lodash')
const m = require('mochainon')
const angular = require('angular')
require('angular-mocks')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../../lib/gui/app/modules/utils')
describe('Browser: DriveSelector', function () {

View File

@ -18,8 +18,9 @@
const m = require('mochainon')
const _ = require('lodash')
const Bluebird = require('bluebird')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../lib/gui/app/models/settings')
// eslint-disable-next-line node/no-missing-require
const localSettings = require('../../../lib/gui/app/models/local-settings')
describe('Browser: settings', function () {
@ -73,12 +74,14 @@ describe('Browser: settings', function () {
})
describe('.assign()', function () {
it('should throw if no settings', function (done) {
settings.assign().asCallback((error) => {
it('should throw if no settings', async () => {
try {
await settings.assign()
m.chai.expect(true).to.be.false
} catch (error) {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Missing settings')
done()
})
m.chai.expect(error.message).to.equal('Settings must be an object')
}
})
it('should not override all settings', function () {
@ -108,23 +111,22 @@ describe('Browser: settings', function () {
})
})
it('should not change the application state if storing to the local machine results in an error', function (done) {
settings.set('foo', 'bar').then(() => {
m.chai.expect(settings.get('foo')).to.equal('bar')
it('should not change the application state if storing to the local machine results in an error', async () => {
await settings.set('foo', 'bar')
m.chai.expect(settings.get('foo')).to.equal('bar')
const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll')
localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error')))
const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll')
localSettingsWriteAllStub.returns(Promise.reject(new Error('localSettings error')))
settings.assign({
foo: 'baz'
}).asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('localSettings error')
localSettingsWriteAllStub.restore()
m.chai.expect(settings.get('foo')).to.equal('bar')
done()
})
}).catch(done)
try {
await settings.assign({ foo: 'baz' })
m.chai.expect(true).to.be.false
} catch (error) {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('localSettings error')
}
localSettingsWriteAllStub.restore()
m.chai.expect(settings.get('foo')).to.equal('bar')
})
})
@ -160,28 +162,34 @@ describe('Browser: settings', function () {
})
})
it('should reject if no key', function (done) {
settings.set(null, true).asCallback((error) => {
it('should reject if no key', async () => {
try {
await settings.set(null, true)
m.chai.expect(true).to.be.false
} catch (error) {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Missing setting key')
done()
})
m.chai.expect(error.message).to.equal('Invalid setting key: null')
}
})
it('should throw if key is not a string', function (done) {
settings.set(1234, true).asCallback((error) => {
it('should throw if key is not a string', async () => {
try {
await settings.set(1234, true)
m.chai.expect(true).to.be.false
} catch (error) {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Invalid setting key: 1234')
done()
})
}
})
it('should throw if setting an array', function (done) {
settings.assign([ 1, 2, 3 ]).asCallback((error) => {
it('should throw if setting an array', async () => {
try {
await settings.assign([ 1, 2, 3 ])
m.chai.expect(true).to.be.false
} catch (error) {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('Settings must be an object')
done()
})
}
})
it('should set the key to undefined if no value', function () {
@ -202,21 +210,22 @@ describe('Browser: settings', function () {
})
})
it('should not change the application state if storing to the local machine results in an error', function (done) {
settings.set('foo', 'bar').then(() => {
it('should not change the application state if storing to the local machine results in an error', async () => {
await settings.set('foo', 'bar')
m.chai.expect(settings.get('foo')).to.equal('bar')
const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll')
localSettingsWriteAllStub.returns(Promise.reject(new Error('localSettings error')))
try {
await settings.set('foo', 'baz')
m.chai.expect(true).to.be.false
} catch (error) {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('localSettings error')
localSettingsWriteAllStub.restore()
m.chai.expect(settings.get('foo')).to.equal('bar')
const localSettingsWriteAllStub = m.sinon.stub(localSettings, 'writeAll')
localSettingsWriteAllStub.returns(Bluebird.reject(new Error('localSettings error')))
settings.set('foo', 'baz').asCallback((error) => {
m.chai.expect(error).to.be.an.instanceof(Error)
m.chai.expect(error.message).to.equal('localSettings error')
localSettingsWriteAllStub.restore()
m.chai.expect(settings.get('foo')).to.equal('bar')
done()
})
}).catch(done)
}
})
})

View File

@ -1,6 +1,7 @@
'use strict'
const m = require('mochainon')
// eslint-disable-next-line node/no-missing-require
const settings = require('../../../lib/gui/app/models/settings')
const progressStatus = require('../../../lib/gui/app/modules/progress-status')

View File

@ -18,6 +18,7 @@
const m = require('mochainon')
const _ = require('lodash')
// eslint-disable-next-line node/no-missing-require
const errors = require('../../lib/gui/app/modules/errors')
describe('Shared: Errors', function () {
@ -64,16 +65,6 @@ describe('Shared: Errors', function () {
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred')
})
it('should return a generic error message if the error is undefined', function () {
const error = undefined
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred')
})
it('should return a generic error message if the error is null', function () {
const error = null
m.chai.expect(errors.getTitle(error)).to.equal('An error ocurred')
})
it('should return the error message', function () {
const error = new Error('This is an error')
m.chai.expect(errors.getTitle(error)).to.equal('This is an error')
@ -325,54 +316,21 @@ describe('Shared: Errors', function () {
m.chai.expect(errors.getDescription(error)).to.equal('Memory error')
})
describe('given userFriendlyDescriptionsOnly is false', function () {
it('should return the stack for a basic error', function () {
const error = new Error('Foo')
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: false
})).to.equal(error.stack)
})
it('should return the stack if the description is an empty string', function () {
const error = new Error('Foo')
error.description = ''
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: false
})).to.equal(error.stack)
})
it('should return the stack if the description is a blank string', function () {
const error = new Error('Foo')
error.description = ' '
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: false
})).to.equal(error.stack)
})
it('should return the stack for a basic error', function () {
const error = new Error('Foo')
m.chai.expect(errors.getDescription(error)).to.equal(error.stack)
})
describe('given userFriendlyDescriptionsOnly is true', function () {
it('should return an empty string for a basic error', function () {
const error = new Error('Foo')
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: true
})).to.equal('')
})
it('should return the stack if the description is an empty string', function () {
const error = new Error('Foo')
error.description = ''
m.chai.expect(errors.getDescription(error)).to.equal(error.stack)
})
it('should return an empty string if the description is an empty string', function () {
const error = new Error('Foo')
error.description = ''
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: true
})).to.equal('')
})
it('should return an empty string if the description is a blank string', function () {
const error = new Error('Foo')
error.description = ' '
m.chai.expect(errors.getDescription(error, {
userFriendlyDescriptionsOnly: true
})).to.equal('')
})
it('should return the stack if the description is a blank string', function () {
const error = new Error('Foo')
error.description = ' '
m.chai.expect(errors.getDescription(error)).to.equal(error.stack)
})
})

View File

@ -18,6 +18,7 @@
const _ = require('lodash')
const m = require('mochainon')
// eslint-disable-next-line node/no-missing-require
const utils = require('../../lib/gui/app/modules/utils')
describe('Shared: Utils', function () {

View File

@ -4,8 +4,10 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"allowJs": true,
"moduleResolution": "node",
"module": "esNext",
"module": "commonjs",
"target": "es2017",
"jsx": "react"
},

View File

@ -138,7 +138,22 @@ const etcherConfig = _.assign(
}
)
const childWriterConfig = _.assign(
{},
etcherConfig,
{
entry: {
etcher: path.join(__dirname, 'lib', 'gui', 'app', 'modules', 'child-writer.js')
},
output: {
path: path.join(__dirname, 'generated'),
filename: 'child-writer.js'
}
}
)
module.exports = [
guiConfig,
etcherConfig
etcherConfig,
childWriterConfig
]