diff --git a/lib/gui/app/modules/image-writer.ts b/lib/gui/app/modules/image-writer.ts index 99e31318..a02ada79 100644 --- a/lib/gui/app/modules/image-writer.ts +++ b/lib/gui/app/modules/image-writer.ts @@ -244,7 +244,6 @@ export async function performWrite( title: 'The writer process ended unexpectedly', description: 'Please try again, and contact the Etcher team if the problem persists', - code: 'ECHILDDIED', }), ); return; diff --git a/lib/gui/modules/child-writer.ts b/lib/gui/modules/child-writer.ts index deeccfb2..623b7b04 100644 --- a/lib/gui/modules/child-writer.ts +++ b/lib/gui/modules/child-writer.ts @@ -17,7 +17,6 @@ import { Drive as DrivelistDrive } from 'drivelist'; import * as sdk from 'etcher-sdk'; import { cleanupTmpFiles } from 'etcher-sdk/build/tmp'; -import * as _ from 'lodash'; import * as ipc from 'node-ipc'; import { toJSON } from '../../shared/errors'; @@ -223,14 +222,14 @@ ipc.connectTo(IPC_SERVER_ID, () => { }); }; - const destinations = _.map(options.destinations, 'device'); + const destinations = options.destinations.map((d) => d.device); log(`Image: ${options.imagePath}`); log(`Devices: ${destinations.join(', ')}`); log(`Umount on success: ${options.unmountOnSuccess}`); log(`Validate on success: ${options.validateWriteOnSuccess}`); log(`Auto blockmapping: ${options.autoBlockmapping}`); log(`Decompress first: ${options.decompressFirst}`); - const dests = _.map(options.destinations, (destination) => { + const dests = options.destinations.map((destination) => { return new sdk.sourceDestination.BlockDevice({ drive: destination, unmountOnSuccess: options.unmountOnSuccess, @@ -261,7 +260,7 @@ ipc.connectTo(IPC_SERVER_ID, () => { onFail, }); log(`Finish: ${results.bytesWritten}`); - results.errors = _.map(results.errors, (error) => { + results.errors = results.errors.map((error) => { return toJSON(error); }); ipc.of[IPC_SERVER_ID].emit('done', { results }); diff --git a/lib/shared/errors.ts b/lib/shared/errors.ts index 30e87172..53721933 100644 --- a/lib/shared/errors.ts +++ b/lib/shared/errors.ts @@ -14,48 +14,37 @@ * limitations under the License. */ -import * as _ from 'lodash'; - -function createErrorDetails(options: { - title: string | ((error: Error & { path: string }) => string); - description: string; -}): { - title: (error: Error & { path: string }) => string; - description: (error: Error & { path: string }) => string; -} { - return _.pick( - _.mapValues(options, (value) => { - return _.isFunction(value) ? value : _.constant(value); - }), - ['title', 'description'], - ); -} +export type ErrorWithPath = Error & { + path?: string; + code?: keyof typeof HUMAN_FRIENDLY; +}; /** * @summary Human-friendly error messages */ export const HUMAN_FRIENDLY = { - ENOENT: createErrorDetails({ - title: (error: Error & { path: string }) => { + ENOENT: { + title: (error: ErrorWithPath) => { return `No such file or directory: ${error.path}`; }, - description: "The file you're trying to access doesn't exist", - }), - EPERM: createErrorDetails({ - title: "You're not authorized to perform this operation", - description: 'Please ensure you have necessary permissions for this task', - }), - EACCES: createErrorDetails({ - title: "You don't have access to this resource", - description: + 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: createErrorDetails({ - title: 'Your system ran out of memory', - description: + }, + ENOMEM: { + title: () => 'Your system ran out of memory', + description: () => 'Please make sure your system has enough available memory for this task', - }), -}; + }, +} as const; /** * @summary Get user friendly property from an error @@ -71,19 +60,21 @@ export const HUMAN_FRIENDLY = { * } */ function getUserFriendlyMessageProperty( - error: Error, + error: ErrorWithPath, property: 'title' | 'description', -): string | null { - const code = _.get(error, ['code']); - - if (_.isNil(code) || !_.isString(code)) { - return null; +): string | undefined { + if (typeof error.code !== 'string') { + return undefined; } - - return _.invoke(HUMAN_FRIENDLY, [code, property], error); + return HUMAN_FRIENDLY[error.code]?.[property]?.(error); } -const isBlank = _.flow([_.trim, _.isEmpty]); +function isBlank(s: string | number | null | undefined) { + if (typeof s === 'number') { + s = s.toString(); + } + return (s ?? '').trim() === ''; +} /** * @summary Get the title of an error @@ -92,23 +83,19 @@ const isBlank = _.flow([_.trim, _.isEmpty]); * Try to get as much information as possible about the error * rather than falling back to generic messages right away. */ -export function getTitle(error: Error): string { - if (!_.isError(error) && !_.isPlainObject(error) && !_.isNil(error)) { - return _.toString(error); - } - +export function getTitle(error: ErrorWithPath): string { const codeTitle = getUserFriendlyMessageProperty(error, 'title'); - if (!_.isNil(codeTitle)) { + if (codeTitle !== undefined) { return codeTitle; } - const message = _.get(error, ['message']); + const message = error.message; if (!isBlank(message)) { return message; } - const code = _.get(error, ['code']); - if (!_.isNil(code) && !isBlank(code)) { + const code = error.code; + if (!isBlank(code)) { return `Error code: ${code}`; } @@ -119,40 +106,19 @@ export function getTitle(error: Error): string { * @summary Get the description of an error */ export function getDescription( - error: Error & { description?: string }, - options: { userFriendlyDescriptionsOnly?: boolean } = {}, + error: ErrorWithPath & { description?: string }, ): string { - _.defaults(options, { - userFriendlyDescriptionsOnly: false, - }); - - if (!_.isError(error) && !_.isPlainObject(error)) { - return ''; - } - if (!isBlank(error.description)) { return error.description as string; } - const codeDescription = getUserFriendlyMessageProperty(error, 'description'); - if (!_.isNil(codeDescription)) { + if (codeDescription !== undefined) { 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); + return JSON.stringify(error, null, 2); } /** @@ -162,24 +128,24 @@ export function createError(options: { title: string; description?: string; report?: boolean; - code?: string; -}): Error & { description?: string; report?: boolean; code?: string } { + code?: keyof typeof HUMAN_FRIENDLY; +}): ErrorWithPath & { description?: string; report?: boolean } { if (isBlank(options.title)) { throw new Error(`Invalid error title: ${options.title}`); } - const error: Error & { + const error: ErrorWithPath & { description?: string; report?: boolean; code?: string; } = new Error(options.title); error.description = options.description; - if (!_.isNil(options.report) && !options.report) { + if (options.report === false) { error.report = false; } - if (!_.isNil(options.code)) { + if (options.code !== undefined) { error.code = options.code; } @@ -198,7 +164,7 @@ export function createError(options: { export function createUserError(options: { title: string; description: string; - code?: string; + code?: keyof typeof HUMAN_FRIENDLY; }): Error { return createError({ title: options.title, @@ -208,13 +174,6 @@ export function createUserError(options: { }); } -/** - * @summary Check if an error is an user error - */ -export function isUserError(error: Error & { report?: boolean }): boolean { - return _.isNil(error.report) ? false : !error.report; -} - /** * @summary Convert an Error object to a JSON object * @function @@ -260,5 +219,5 @@ export function toJSON( * @summary Convert a JSON object to an Error object */ export function fromJSON(json: any): Error { - return _.assign(new Error(json.message), json); + return Object.assign(new Error(json.message), json); } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 0c741f5b..03a53961 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -7019,9 +7019,9 @@ "dev": true }, "etcher-sdk": { - "version": "4.1.20", - "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.20.tgz", - "integrity": "sha512-cVjAifAKKYDc9SNMICnK4YWXHz3uGlwKgYOkRykPATxkwhVYfaW6wlhV2DFqbfkps+l4si7iC82Tz1RaV0VjHA==", + "version": "4.1.21", + "resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-4.1.21.tgz", + "integrity": "sha512-jQzKZxYkUA9+rsmelr6U1d2pBwiRw6AGM//a8EnqL1Aorq44xq2BZ59BomDwofzMJ5RCGEJjgOmi7/w+eUVVvg==", "dev": true, "requires": { "@balena/udif": "^1.0.3", @@ -7035,7 +7035,6 @@ "drivelist": "^9.0.0", "file-disk": "^8.0.0", "file-type": "^8.0.0", - "lodash": "^4.17.19", "lzma-native": "^6.0.0", "mountutils": "^1.3.18", "node-raspberrypi-usbboot": "^0.2.9", diff --git a/package.json b/package.json index dd1b44ef..b2cbf0ea 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "electron-notarize": "^1.0.0", "electron-rebuild": "^1.11.0", "electron-updater": "^4.3.2", - "etcher-sdk": "^4.1.20", + "etcher-sdk": "^4.1.21", "file-loader": "^6.0.0", "husky": "^4.2.5", "immutable": "^3.8.1", diff --git a/tests/shared/errors.spec.ts b/tests/shared/errors.spec.ts index 31da3099..4528e600 100644 --- a/tests/shared/errors.spec.ts +++ b/tests/shared/errors.spec.ts @@ -37,54 +37,12 @@ describe('Shared: Errors', function () { }); describe('.getTitle()', function () { - it('should accept a string', function () { - const error = 'This is an error'; - // @ts-ignore - expect(errors.getTitle(error)).to.equal('This is an error'); - }); - - it('should accept a number 0', function () { - const error = 0; - // @ts-ignore - expect(errors.getTitle(error)).to.equal('0'); - }); - - it('should accept a number 1', function () { - const error = 1; - // @ts-ignore - expect(errors.getTitle(error)).to.equal('1'); - }); - - it('should accept a number -1', function () { - const error = -1; - // @ts-ignore - expect(errors.getTitle(error)).to.equal('-1'); - }); - - it('should accept an array', function () { - const error = [0, 1, 2]; - // @ts-ignore - expect(errors.getTitle(error)).to.equal('0,1,2'); - }); - it('should return a generic error message if the error is an empty object', function () { const error = {}; // @ts-ignore 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; - // @ts-ignore - 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; - // @ts-ignore - expect(errors.getTitle(error)).to.equal('An error ocurred'); - }); - it('should return the error message', function () { const error = new Error('This is an error'); expect(errors.getTitle(error)).to.equal('This is an error'); @@ -234,42 +192,6 @@ describe('Shared: Errors', function () { }); describe('.getDescription()', function () { - it('should return an empty string if the error is a string', function () { - const error = 'My error'; - // @ts-ignore - expect(errors.getDescription(error)).to.equal(''); - }); - - it('should return an empty string if the error is a number', function () { - const error = 0; - // @ts-ignore - expect(errors.getDescription(error)).to.equal(''); - }); - - it('should return an empty string if the error is an array', function () { - const error = [1, 2, 3]; - // @ts-ignore - expect(errors.getDescription(error)).to.equal(''); - }); - - it('should return an empty string if the error is undefined', function () { - const error = undefined; - // @ts-ignore - expect(errors.getDescription(error)).to.equal(''); - }); - - it('should return an empty string if the error is null', function () { - const error = null; - // @ts-ignore - expect(errors.getDescription(error)).to.equal(''); - }); - - it('should return an empty string if the error is an empty object', function () { - const error = {}; - // @ts-ignore - expect(errors.getDescription(error)).to.equal(''); - }); - it('should understand an error-like object with a description', function () { const error = { description: 'My description', @@ -384,122 +306,26 @@ describe('Shared: Errors', function () { describe('given userFriendlyDescriptionsOnly is false', function () { it('should return the stack for a basic error', function () { const error = new Error('Foo'); - expect( - errors.getDescription(error, { - userFriendlyDescriptionsOnly: false, - }), - ).to.equal(error.stack); + expect(errors.getDescription(error)).to.equal(error.stack); }); it('should return the stack if the description is an empty string', function () { const error = new Error('Foo'); // @ts-ignore error.description = ''; - expect( - errors.getDescription(error, { - userFriendlyDescriptionsOnly: false, - }), - ).to.equal(error.stack); + expect(errors.getDescription(error)).to.equal(error.stack); }); it('should return the stack if the description is a blank string', function () { const error = new Error('Foo'); // @ts-ignore error.description = ' '; - expect( - errors.getDescription(error, { - userFriendlyDescriptionsOnly: false, - }), - ).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'); - expect( - errors.getDescription(error, { - userFriendlyDescriptionsOnly: true, - }), - ).to.equal(''); - }); - - it('should return an empty string if the description is an empty string', function () { - const error = new Error('Foo'); - // @ts-ignore - error.description = ''; - 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'); - // @ts-ignore - error.description = ' '; - expect( - errors.getDescription(error, { - userFriendlyDescriptionsOnly: true, - }), - ).to.equal(''); + expect(errors.getDescription(error)).to.equal(error.stack); }); }); }); describe('.createError()', function () { - it('should not be a user error', function () { - const error = errors.createError({ - title: 'Foo', - description: 'Something happened', - }); - - expect(errors.isUserError(error)).to.be.false; - }); - - it('should be a user error if `options.report` is false', function () { - const error = errors.createError({ - title: 'Foo', - description: 'Something happened', - report: false, - }); - - expect(errors.isUserError(error)).to.be.true; - }); - - it('should be a user error if `options.report` evaluates to false', function () { - const error = errors.createError({ - title: 'Foo', - description: 'Something happened', - // @ts-ignore - report: 0, - }); - - expect(errors.isUserError(error)).to.be.true; - }); - - it('should not be a user error if `options.report` is true', function () { - const error = errors.createError({ - title: 'Foo', - description: 'Something happened', - report: true, - }); - - expect(errors.isUserError(error)).to.be.false; - }); - - it('should not be a user error if `options.report` evaluates to true', function () { - const error = errors.createError({ - title: 'Foo', - description: 'Something happened', - // @ts-ignore - report: 1, - }); - - expect(errors.isUserError(error)).to.be.false; - }); - it('should be an instance of Error', function () { const error = errors.createError({ title: 'Foo', @@ -523,10 +349,10 @@ describe('Shared: Errors', function () { const error = errors.createError({ title: 'Foo', description: 'Something happened', - code: 'HELLO', + code: 'ENOENT', }); - expect(error.code).to.equal('HELLO'); + expect(error.code).to.equal('ENOENT'); }); it('should correctly add only a title', function () { @@ -590,15 +416,6 @@ describe('Shared: Errors', function () { }); describe('.createUserError()', function () { - it('should be a user error', function () { - const error = errors.createUserError({ - title: 'Foo', - description: 'Something happened', - }); - - expect(errors.isUserError(error)).to.be.true; - }); - it('should be an instance of Error', function () { const error = errors.createUserError({ title: 'Foo', @@ -632,11 +449,11 @@ describe('Shared: Errors', function () { // @ts-ignore const error = errors.createUserError({ title: 'Foo', - code: 'HELLO', + code: 'ENOENT', }); // @ts-ignore - expect(error.code).to.equal('HELLO'); + expect(error.code).to.equal('ENOENT'); }); it('should ignore an empty description', function () { @@ -692,26 +509,6 @@ describe('Shared: Errors', function () { }); }); - describe('.isUserError()', function () { - _.each([0, '', false], (value) => { - it(`should return true if report equals ${value}`, function () { - const error = new Error('foo bar'); - // @ts-ignore - error.report = value; - expect(errors.isUserError(error)).to.be.true; - }); - }); - - _.each([undefined, null, true, 1, 3, 'foo'], (value) => { - it(`should return false if report equals ${value}`, function () { - const error = new Error('foo bar'); - // @ts-ignore - error.report = value; - expect(errors.isUserError(error)).to.be.false; - }); - }); - }); - describe('.toJSON()', function () { it('should convert a simple error', function () { const error = new Error('My error');