From 8cb42678aa60f71b686ba8741957845df9847ee1 Mon Sep 17 00:00:00 2001 From: Ross Stenersen Date: Thu, 22 May 2025 08:57:25 -0500 Subject: [PATCH] refactor: convert devices:capability-status command to yargs --- .../src/commands/devices/component-status.ts | 53 -------------- .../lib/command/util/devices-table.test.ts | 62 ++++++++++++++++- .../devicepreferences/translations.ts | 1 - src/commands/devices/component-status.ts | 69 +++++++++++++++++++ src/commands/index.ts | 2 + src/lib/command/util/devices-table.ts | 16 ++++- 6 files changed, 146 insertions(+), 57 deletions(-) delete mode 100644 packages/cli/src/commands/devices/component-status.ts create mode 100644 src/commands/devices/component-status.ts diff --git a/packages/cli/src/commands/devices/component-status.ts b/packages/cli/src/commands/devices/component-status.ts deleted file mode 100644 index 444e6df5..00000000 --- a/packages/cli/src/commands/devices/component-status.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ComponentStatus } from '@smartthings/core-sdk' - -import { APICommand, chooseComponent, chooseDevice, formatAndWriteItem, TableGenerator } from '@smartthings/cli-lib' - -import { prettyPrintAttribute } from '../../lib/commands/devices-util.js' - - -function buildTableOutput(tableGenerator: TableGenerator, component: ComponentStatus): string { - const table = tableGenerator.newOutputTable({ head: ['Capability', 'Attribute', 'Value'] }) - for (const capabilityName of Object.keys(component)) { - const capability = component[capabilityName] - for (const attributeName of Object.keys(capability)) { - const attribute = capability[attributeName] - table.push([ - capabilityName, - attributeName, - prettyPrintAttribute(attribute), - ]) - } - } - return table.toString() -} - -export default class DeviceComponentStatusCommand extends APICommand { - static description = "get the current status of a device component's attributes" + - this.apiDocsURL('getDeviceComponentStatus') - - static flags = { - ...APICommand.flags, - ...formatAndWriteItem.flags, - } - - static args = [ - { - name: 'id', - description: 'the device id', - }, - { - name: 'component', - description: 'the component id', - }, - ] - - async run(): Promise { - const deviceId = await chooseDevice(this, this.args.id, { allowIndex: true }) - - const device = await this.client.devices.get(deviceId) - const componentName = await chooseComponent(this, this.args.component, device.components) - - const componentStatus = await this.client.devices.getComponentStatus(deviceId, componentName) - await formatAndWriteItem(this, { buildTableOutput: data => buildTableOutput(this.tableGenerator, data) }, componentStatus) - } -} diff --git a/src/__tests__/lib/command/util/devices-table.test.ts b/src/__tests__/lib/command/util/devices-table.test.ts index d4bddac9..526983d2 100644 --- a/src/__tests__/lib/command/util/devices-table.test.ts +++ b/src/__tests__/lib/command/util/devices-table.test.ts @@ -1,6 +1,10 @@ import type { Device, HubDeviceDetails, MatterDeviceDetails } from '@smartthings/core-sdk' -import type { TableFieldDefinition, ValueTableFieldDefinition } from '../../../../lib/table-generator.js' +import { + defaultTableGenerator, + type TableFieldDefinition, + type ValueTableFieldDefinition, +} from '../../../../lib/table-generator.js' import { buildTableFromItemMock, tableGeneratorMock, @@ -10,6 +14,7 @@ import { const { + buildComponentStatusTableOutput, buildEmbeddedStatusTableOutput, buildTableOutput, } = await import('../../../../lib/command/util/devices-table.js') @@ -157,7 +162,7 @@ describe('buildTableOutput', () => { it('joins multiple children with newlines', () => { const device = { ...baseDevice, - childDevices: [{ id: 'child-id-1' }, { id: 'child-id-2' }], + childDevices: [{ id: 'child-id-1' }, { id: 'child-id-2' }], } as Device tableToStringMock.mockReturnValueOnce('main table') @@ -618,3 +623,56 @@ describe('buildTableOutput', () => { ['name', { prop: 'hubId', skipEmpty: true }, { prop: 'driverId', skipEmpty: true }]) }) }) + +describe('buildComponentStatusTableOutput', () => { + const tableGenerator = defaultTableGenerator({ groupRows: false }) + + it('displays empty table when given no components', () => { + expect(buildComponentStatusTableOutput(tableGenerator, {})).toBe( + '──────────────────────────────\n' + + ' Capability Attribute Value \n' + + '──────────────────────────────\n', + ) + }) + + it('displays populated table for complex component', () => { + expect(buildComponentStatusTableOutput(tableGenerator, { + button: { + button: { + value: 'held', + timestamp: '2022-08-02T20:18:49.232Z', + }, + numberOfButtons: { + value: 3, + timestamp: '2022-08-02T20:18:49.232Z', + }, + supportedButtonValues: { + value: [ + 'up_5x', + ], + timestamp: '2022-08-02T20:18:49.232Z', + }, + }, + temperatureMeasurement: { + temperatureRange: { + value: null, + }, + temperature: { + value: 152.99142132163604, + unit: 'F', + timestamp: '2022-08-02T20:18:49.232Z', + }, + }, + })).toBe( + '─────────────────────────────────────────────────────────────────────\n' + + ' Capability Attribute Value \n' + + '─────────────────────────────────────────────────────────────────────\n' + + ' button button "held" \n' + + ' button numberOfButtons 3 \n' + + ' button supportedButtonValues ["up_5x"] \n' + + ' temperatureMeasurement temperatureRange \n' + + ' temperatureMeasurement temperature 152.99142132163604 F \n' + + '─────────────────────────────────────────────────────────────────────\n', + ) + }) +}) diff --git a/src/commands/devicepreferences/translations.ts b/src/commands/devicepreferences/translations.ts index 344a738d..8f8348c1 100644 --- a/src/commands/devicepreferences/translations.ts +++ b/src/commands/devicepreferences/translations.ts @@ -51,7 +51,6 @@ const builder = (yargs: Argv): Argv => ], ]) - const handler = async (argv: ArgumentsCamelCase): Promise => { const command = await apiOrganizationCommand(argv) diff --git a/src/commands/devices/component-status.ts b/src/commands/devices/component-status.ts new file mode 100644 index 00000000..b54b8ed1 --- /dev/null +++ b/src/commands/devices/component-status.ts @@ -0,0 +1,69 @@ +import { type ArgumentsCamelCase, type Argv, type CommandModule } from 'yargs' + +import { + apiCommand, + apiCommandBuilder, + apiDocsURL, + type APICommandFlags, +} from '../../lib/command/api-command.js' +import { + formatAndWriteItem, + formatAndWriteItemBuilder, + type FormatAndWriteItemFlags, +} from '../../lib/command/format.js' +import { chooseComponentFn, chooseDevice } from '../../lib/command/util/devices-choose.js' +import { buildComponentStatusTableOutput } from '../../lib/command/util/devices-table.js' + + +export type CommandArgs = + & APICommandFlags + & FormatAndWriteItemFlags + & { + deviceIdOrIndex?: string + componentId?: string + } + +const command = 'devices:component-status [device-id-or-index] [component-id]' + +const describe = "get the current status of a device component's attributes" + +const builder = (yargs: Argv): Argv => + formatAndWriteItemBuilder(apiCommandBuilder(yargs)) + .positional('device-id-or-index', + { describe: 'device id or index in list from devices command', type: 'string' }) + .positional('component-id', { describe: 'component id', type: 'string' }) + .example([ + ['$0 devices:capability-status', + 'prompt for a device and component and display its status'], + ['$0 devices:capability-status fa1eb54c-c571-405f-8817-ffb7cd2f5a9d', + 'prompt for a component for the specified device and display its status'], + ['$0 devices:capability-status 12', + 'prompt for a component for the twelfth device found when running "smartthings devices"' + + ' and display its status'], + ['$0 devices:capability-status fa1eb54c-c571-405f-8817-ffb7cd2f5a9d main', + 'display the status for the specified device and component'], + ]) + .epilog(apiDocsURL('getDeviceComponentStatus')) + +const handler = async (argv: ArgumentsCamelCase): Promise => { + const command = await apiCommand(argv) + + const deviceId = await chooseDevice(command, argv.deviceIdOrIndex, { allowIndex: true }) + + const device = await command.client.devices.get(deviceId) + const componentName = await chooseComponentFn(device)( + command, + argv.componentId, + { autoChoose: true, allowIndex: true }, + ) + + const componentStatus = await command.client.devices.getComponentStatus(deviceId, componentName) + await formatAndWriteItem( + command, + { buildTableOutput: data => buildComponentStatusTableOutput(command.tableGenerator, data) }, + componentStatus, + ) +} + +const cmd: CommandModule = { command, describe, builder, handler } +export default cmd diff --git a/src/commands/index.ts b/src/commands/index.ts index f3c891b1..a480b626 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -36,6 +36,7 @@ import deviceprofilesCreateCommand from './deviceprofiles/create.js' import deviceprofilesViewCommand from './deviceprofiles/view.js' import devicesCommand from './devices.js' import devicesCapabilityStatusCommand from './devices/capability-status.js' +import devicesComponentStatusCommand from './devices/component-status.js' import devicesCommandsCommand from './devices/commands.js' import devicesDeleteCommand from './devices/delete.js' import devicesHistoryCommand from './devices/history.js' @@ -130,6 +131,7 @@ export const commands: CommandModule[] = [ deviceprofilesViewCommand, devicesCommand, devicesCapabilityStatusCommand, + devicesComponentStatusCommand, devicesCommandsCommand, devicesDeleteCommand, devicesHistoryCommand, diff --git a/src/lib/command/util/devices-table.ts b/src/lib/command/util/devices-table.ts index 7f54d34f..d2a34e3b 100644 --- a/src/lib/command/util/devices-table.ts +++ b/src/lib/command/util/devices-table.ts @@ -1,4 +1,4 @@ -import { type Device, type DeviceHealth } from '@smartthings/core-sdk' +import { type ComponentStatus, type Device, type DeviceHealth } from '@smartthings/core-sdk' import { type WithNamedRoom } from '../../api-helpers.js' import { type TableGenerator } from '../../table-generator.js' @@ -201,3 +201,17 @@ export const buildTableOutput = ( (statusInfo ? `\n\nDevice Status\n${statusInfo}` : '') + (infoFrom ? `\n\nDevice Integration Info (from ${infoFrom})\n${deviceIntegrationInfo}` : '') } + +export const buildComponentStatusTableOutput = ( + tableGenerator: TableGenerator, + componentStatus: ComponentStatus, +): string => { + const asList = Object.entries(componentStatus).flatMap(([capabilityName, capabilityStatus]) => + Object.entries(capabilityStatus).map(([attribute, state]) => ({ capabilityName, attribute, state })), + ) + return tableGenerator.buildTableFromList(asList, [ + { prop: 'capabilityName', label: 'Capability' }, + 'attribute', + { label: 'Value', value: attribute => prettyPrintAttribute(attribute.state) }, + ]) +}