From 55bd282861fc4947fc78076cb996ba068e2eb6ec Mon Sep 17 00:00:00 2001 From: John Undersander Date: Fri, 1 Apr 2022 15:39:02 -0500 Subject: [PATCH] perf: replace lodash with native ES or lodash modules --- .changeset/giant-poems-reflect.md | 6 + package-lock.json | 114 ++++++++++++------ packages/cli/package.json | 6 +- packages/cli/src/commands/apps/register.ts | 5 +- packages/cli/src/commands/installedschema.ts | 10 +- packages/lib/package.json | 6 +- .../lib/src/__tests__/table-generator.test.ts | 23 ++-- packages/lib/src/api-helpers.ts | 5 +- packages/lib/src/table-generator.ts | 11 +- 9 files changed, 117 insertions(+), 69 deletions(-) create mode 100644 .changeset/giant-poems-reflect.md diff --git a/.changeset/giant-poems-reflect.md b/.changeset/giant-poems-reflect.md new file mode 100644 index 000000000..28ad38fa7 --- /dev/null +++ b/.changeset/giant-poems-reflect.md @@ -0,0 +1,6 @@ +--- +"@smartthings/cli": patch +"@smartthings/cli-lib": patch +--- + +replace usage of lodash with native ES or separate lodash modules diff --git a/package-lock.json b/package-lock.json index cf5483d12..aaa0b6556 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3577,16 +3577,17 @@ "link": true }, "node_modules/@smartthings/core-sdk": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@smartthings/core-sdk/-/core-sdk-3.4.0.tgz", - "integrity": "sha512-IBdaHvbLuByzze+efYjyIDngKdJF5YOe3b/tp4vlo9GXMkrLQGsXMdykM7F4vCw+N3LfdPJMWvhPOVcQ5W00RA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@smartthings/core-sdk/-/core-sdk-3.4.1.tgz", + "integrity": "sha512-5pincw51e/nSpn+YanJlBYn/sTx8Nx5xnGWLntJsdUKv6WZM3gu29+6WyrCKpsFywu0FBHwYND0KS/ieZ8J0pA==", "dependencies": { "async-mutex": "^0.3.2", "axios": "^0.21.4", "http-signature": "^1.3.6", + "lodash.isdate": "^4.0.1", + "lodash.isstring": "^4.0.1", "qs": "^6.10.3", - "sshpk": "^1.17.0", - "underscore": "^1.13.2" + "sshpk": "^1.17.0" }, "engines": { "node": ">=12.0.0" @@ -3868,11 +3869,20 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "version": "4.14.181", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz", + "integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==", "dev": true }, + "node_modules/@types/lodash.at": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.at/-/lodash.at-4.6.6.tgz", + "integrity": "sha512-OAfIqBWv0HjWVxKF8y6ZFTEwLM1wgpvsrMOb/fEKp8/HM2JO2V555Au5JZkiTnXOF453rqCooGKYFzNrR/kaAA==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -11628,6 +11638,21 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=" + }, + "node_modules/lodash.isdate": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isdate/-/lodash.isdate-4.0.1.tgz", + "integrity": "sha1-NaVDZzuddhEN5BFLMsxXcEin82Y=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "node_modules/lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -16258,11 +16283,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/underscore": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", - "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==" - }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -17742,13 +17762,12 @@ "@oclif/plugin-not-found": "^2.3.1", "@oclif/plugin-plugins": "^2.1.0", "@smartthings/cli-lib": "^1.0.0-beta.0", - "@smartthings/core-sdk": "^3.4.0", + "@smartthings/core-sdk": "^3.4.1", "@smartthings/plugin-cli-edge": "^1.10.1", "aws-sdk": "^2.1073.0", "cli-table": "^0.3.11", "inquirer": "^8.2.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21" + "js-yaml": "^4.1.0" }, "bin": { "smartthings": "bin/run" @@ -17759,7 +17778,6 @@ "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.178", "@types/node": "^16.11.24", "@typescript-eslint/eslint-plugin": "^5.11.0", "@typescript-eslint/parser": "^5.11.0", @@ -17787,7 +17805,7 @@ "license": "Apache-2.0", "dependencies": { "@oclif/core": "^1.5.1", - "@smartthings/core-sdk": "^3.4.0", + "@smartthings/core-sdk": "^3.4.1", "@types/eventsource": "^1.1.8", "axios": "^0.21.4", "chalk": "^4.1.2", @@ -17797,7 +17815,7 @@ "get-port": "^5.1.1", "inquirer": "^8.2.0", "js-yaml": "^4.1.0", - "lodash": "^4.17.21", + "lodash.at": "^4.6.0", "log4js": "6.3.0", "open": "^8.4.0", "os-locale": "^5.0.0", @@ -17809,7 +17827,7 @@ "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.178", + "@types/lodash.at": "^4.6.6", "@types/node": "^16.11.24", "@types/qs": "^6.9.7", "@typescript-eslint/eslint-plugin": "^5.11.0", @@ -20734,13 +20752,12 @@ "@oclif/plugin-plugins": "^2.1.0", "@smartthings/cli-lib": "^1.0.0-beta.0", "@smartthings/cli-testlib": "^1.0.0-beta.1", - "@smartthings/core-sdk": "^3.4.0", + "@smartthings/core-sdk": "^3.4.1", "@smartthings/plugin-cli-edge": "^1.10.1", "@types/cli-table": "^0.3.0", "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.178", "@types/node": "^16.11.24", "@typescript-eslint/eslint-plugin": "^5.11.0", "@typescript-eslint/parser": "^5.11.0", @@ -20754,7 +20771,6 @@ "jest": "^27.5.1", "jest-extended": "^0.11.5", "js-yaml": "^4.1.0", - "lodash": "^4.17.21", "oclif": "^2.5.0", "pkg": "4.4.9", "rimraf": "^3.0.2", @@ -20767,14 +20783,14 @@ "version": "file:packages/lib", "requires": { "@oclif/core": "^1.5.1", - "@smartthings/core-sdk": "^3.4.0", + "@smartthings/core-sdk": "^3.4.1", "@types/cli-table": "^0.3.0", "@types/eventsource": "^1.1.8", "@types/express": "^4.17.13", "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.178", + "@types/lodash.at": "^4.6.6", "@types/node": "^16.11.24", "@types/qs": "^6.9.7", "@typescript-eslint/eslint-plugin": "^5.11.0", @@ -20793,7 +20809,7 @@ "jest": "^27.5.1", "jest-extended": "^0.11.5", "js-yaml": "^4.1.0", - "lodash": "^4.17.21", + "lodash.at": "^4.6.0", "log4js": "6.3.0", "mock-stdin": "^1.0.0", "oclif": "^2.5.0", @@ -20828,16 +20844,17 @@ } }, "@smartthings/core-sdk": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@smartthings/core-sdk/-/core-sdk-3.4.0.tgz", - "integrity": "sha512-IBdaHvbLuByzze+efYjyIDngKdJF5YOe3b/tp4vlo9GXMkrLQGsXMdykM7F4vCw+N3LfdPJMWvhPOVcQ5W00RA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@smartthings/core-sdk/-/core-sdk-3.4.1.tgz", + "integrity": "sha512-5pincw51e/nSpn+YanJlBYn/sTx8Nx5xnGWLntJsdUKv6WZM3gu29+6WyrCKpsFywu0FBHwYND0KS/ieZ8J0pA==", "requires": { "async-mutex": "^0.3.2", "axios": "^0.21.4", "http-signature": "^1.3.6", + "lodash.isdate": "^4.0.1", + "lodash.isstring": "^4.0.1", "qs": "^6.10.3", - "sshpk": "^1.17.0", - "underscore": "^1.13.2" + "sshpk": "^1.17.0" } }, "@smartthings/plugin-cli-edge": { @@ -21109,11 +21126,20 @@ "dev": true }, "@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "version": "4.14.181", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz", + "integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==", "dev": true }, + "@types/lodash.at": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.at/-/lodash.at-4.6.6.tgz", + "integrity": "sha512-OAfIqBWv0HjWVxKF8y6ZFTEwLM1wgpvsrMOb/fEKp8/HM2JO2V555Au5JZkiTnXOF453rqCooGKYFzNrR/kaAA==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -27110,6 +27136,21 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=" + }, + "lodash.isdate": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isdate/-/lodash.isdate-4.0.1.tgz", + "integrity": "sha1-NaVDZzuddhEN5BFLMsxXcEin82Y=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -30689,11 +30730,6 @@ "which-boxed-primitive": "^1.0.2" } }, - "underscore": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", - "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==" - }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", diff --git a/packages/cli/package.json b/packages/cli/package.json index f743b45f0..15aa5b898 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -66,13 +66,12 @@ "@oclif/plugin-not-found": "^2.3.1", "@oclif/plugin-plugins": "^2.1.0", "@smartthings/cli-lib": "^1.0.0-beta.0", - "@smartthings/core-sdk": "^3.4.0", + "@smartthings/core-sdk": "^3.4.1", "@smartthings/plugin-cli-edge": "^1.10.1", "aws-sdk": "^2.1073.0", "cli-table": "^0.3.11", "inquirer": "^8.2.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21" + "js-yaml": "^4.1.0" }, "devDependencies": { "@smartthings/cli-testlib": "^1.0.0-beta.1", @@ -80,7 +79,6 @@ "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.178", "@types/node": "^16.11.24", "@typescript-eslint/eslint-plugin": "^5.11.0", "@typescript-eslint/parser": "^5.11.0", diff --git a/packages/cli/src/commands/apps/register.ts b/packages/cli/src/commands/apps/register.ts index 4962da459..13094943b 100644 --- a/packages/cli/src/commands/apps/register.ts +++ b/packages/cli/src/commands/apps/register.ts @@ -1,4 +1,3 @@ -import _ from 'lodash' import { App, AppType } from '@smartthings/core-sdk' import { APICommand, selectFromList, SelectingConfig } from '@smartthings/cli-lib' @@ -24,10 +23,10 @@ export default class AppRegisterCommand extends APICommand { } const id = await selectFromList(this, config, { preselectedId: args.id, - listItems: async () => _.flatten(await Promise.all([ + listItems: async () => (await Promise.all([ this.client.apps.list({ appType: AppType.WEBHOOK_SMART_APP }), this.client.apps.list({ appType: AppType.API_ONLY }), - ])), + ])).flat(), promptMessage: 'Select an app to register.', }) await this.client.apps.register(id), diff --git a/packages/cli/src/commands/installedschema.ts b/packages/cli/src/commands/installedschema.ts index 53a190aa0..ca185e715 100644 --- a/packages/cli/src/commands/installedschema.ts +++ b/packages/cli/src/commands/installedschema.ts @@ -1,5 +1,3 @@ -import _ from 'lodash' - import { Flags } from '@oclif/core' import { InstalledSchemaApp, SmartThingsClient } from '@smartthings/core-sdk' @@ -20,18 +18,18 @@ export async function installedSchemaInstances(client: SmartThingsClient, locati locationIds = (await client.locations.list()).map(it => it.locationId) } - const isas = _.flatten(await Promise.all(locationIds.map(async (locationId) => { + const installedApps = (await Promise.all(locationIds.map(async (locationId) => { try { return (await client.schema.installedApps(locationId)) } catch(e) { return [] } - }))) + }))).flat() if (verbose) { - return await withLocations(client, isas) + return await withLocations(client, installedApps) } - return isas + return installedApps } export default class InstalledSchemaAppsCommand extends APICommand { diff --git a/packages/lib/package.json b/packages/lib/package.json index 734a1dd50..993ad7232 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@oclif/core": "^1.5.1", - "@smartthings/core-sdk": "^3.4.0", + "@smartthings/core-sdk": "^3.4.1", "@types/eventsource": "^1.1.8", "axios": "^0.21.4", "chalk": "^4.1.2", @@ -39,7 +39,7 @@ "get-port": "^5.1.1", "inquirer": "^8.2.0", "js-yaml": "^4.1.0", - "lodash": "^4.17.21", + "lodash.at": "^4.6.0", "log4js": "6.3.0", "open": "^8.4.0", "os-locale": "^5.0.0", @@ -51,7 +51,7 @@ "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.178", + "@types/lodash.at": "^4.6.6", "@types/node": "^16.11.24", "@types/qs": "^6.9.7", "@typescript-eslint/eslint-plugin": "^5.11.0", diff --git a/packages/lib/src/__tests__/table-generator.test.ts b/packages/lib/src/__tests__/table-generator.test.ts index 998c4b97b..7b965ee62 100644 --- a/packages/lib/src/__tests__/table-generator.test.ts +++ b/packages/lib/src/__tests__/table-generator.test.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import at from 'lodash.at' import { URL } from 'url' import { logManager } from '../logger' @@ -8,6 +8,13 @@ import { debugMock, warnMock } from './test-lib/mock-logger' jest.mock('../logger') +jest.mock('lodash.at', () => { + const actualAt = jest.requireActual('lodash.at') + return { + __esModule: true, + default: jest.fn(actualAt), + } +}) /** * Quote characters that are special to regular expressions. @@ -137,6 +144,8 @@ const basicFieldDefinitions: TableFieldDefinition[] = [ ] describe('tableGenerator', () => { + const mockAt = jest.mocked(at) + let tableGenerator: TableGenerator beforeEach(() => { @@ -287,29 +296,29 @@ describe('tableGenerator', () => { }) it('uses empty string for no match', () => { - const atSpy = jest.spyOn(_, 'at').mockReturnValue([]) + mockAt.mockReturnValue([]) const output = tableGenerator.buildTableFromList([{}], ['fieldName']) expect(output).toHaveItemValues(['']) expect(logManager.getLogger).toHaveBeenCalledTimes(1) expect(logManager.getLogger).toHaveBeenCalledWith('table-manager') - expect(atSpy).toHaveBeenCalledTimes(1) - expect(atSpy).toHaveBeenCalledWith({}, 'fieldName') + expect(mockAt).toHaveBeenCalledTimes(1) + expect(mockAt).toHaveBeenCalledWith({}, 'fieldName') expect(debugMock).toHaveBeenCalledTimes(1) expect(debugMock).toHaveBeenCalledWith('did not find match for fieldName in {}') }) it('combines data on multiple matches', () => { - const atSpy = jest.spyOn(_, 'at').mockReturnValue(['one', 'two']) + mockAt.mockReturnValue(['one', 'two']) const output = tableGenerator.buildTableFromList([{}], ['fieldName']) expect(output).toHaveItemValues(['one, two']) expect(logManager.getLogger).toHaveBeenCalledTimes(1) expect(logManager.getLogger).toHaveBeenCalledWith('table-manager') - expect(atSpy).toHaveBeenCalledTimes(1) - expect(atSpy).toHaveBeenCalledWith({}, 'fieldName') + expect(mockAt).toHaveBeenCalledTimes(1) + expect(mockAt).toHaveBeenCalledWith({}, 'fieldName') expect(warnMock).toHaveBeenCalledTimes(1) expect(warnMock).toHaveBeenCalledWith('found more than one match for fieldName in {}') }) diff --git a/packages/lib/src/api-helpers.ts b/packages/lib/src/api-helpers.ts index e9ab8f67a..f5d7345b1 100644 --- a/packages/lib/src/api-helpers.ts +++ b/packages/lib/src/api-helpers.ts @@ -1,4 +1,3 @@ -import _ from 'lodash' import { SmartThingsClient, OrganizationResponse, CapabilityNamespace } from '@smartthings/core-sdk' @@ -42,12 +41,12 @@ function notEmpty(value: T | null | undefined): value is T { return value !== null && value !== undefined } -function uniqueLocationIds(from: WithLocation[]): string[] { +function uniqueLocationIds(list: WithLocation[]): string[] { // Note -- the `.filter(notEmpty))` is here because the source types such // as InstalledApp are currently defined with optional locationId even // though locationId is actually always set. The filter can be removed // once that issue is corrected. - return _.uniq(from.map(it => it.locationId).filter(notEmpty)) + return Array.from(new Set(list.map(item => item.locationId).filter(notEmpty))) } export async function withLocationsAndRooms(client: SmartThingsClient, diff --git a/packages/lib/src/table-generator.ts b/packages/lib/src/table-generator.ts index 2a17c9d45..a2fb4c7c5 100644 --- a/packages/lib/src/table-generator.ts +++ b/packages/lib/src/table-generator.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import at from 'lodash.at' import Table from 'cli-table' import { Logger } from '@smartthings/core-sdk' @@ -67,10 +67,10 @@ export type TableFieldDefinition = string | { * The name of the property from which to get data. This reference a nested * property if desired. * - * The Lodash _.at function is used to access the property but any path + * The lodash.at function is used to access the property but any path * used should return a single value. * - * https://lodash.com/docs/4.17.15#at + * https://lodash.com/docs#at * * The default label is also derived from this field when the `label` * property is not included. Only the final property in the path is used. @@ -168,6 +168,7 @@ export class DefaultTableGenerator implements TableGenerator { return this.convertToLabel(definition.prop) } + private getDisplayValueFor(item: T, definition: TableFieldDefinition): string { if (!(typeof definition === 'string') && definition.value) { return definition.value(item) ?? '' @@ -178,9 +179,11 @@ export class DefaultTableGenerator implements TableGenerator { throw Error('both label and value are required if prop is not specified') } + // No types satisfy the lodash.at documented (Object, string|string[]) + // Here we are passing (Object, string) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const matches = _.at(item, propertyName) + const matches = at(item, propertyName) if (matches.length === 0) { this.logger.debug(`did not find match for ${propertyName} in ${JSON.stringify(item)}`) return ''