diff --git a/.changeset/two-emus-study.md b/.changeset/two-emus-study.md new file mode 100644 index 000000000..5a7723f87 --- /dev/null +++ b/.changeset/two-emus-study.md @@ -0,0 +1,5 @@ +--- +"@smartthings/cli-lib": patch +--- + +added support for removing managed config values diff --git a/packages/lib/src/__tests__/cli-config.test.ts b/packages/lib/src/__tests__/cli-config.test.ts index 53993e8e0..36ff26e6e 100644 --- a/packages/lib/src/__tests__/cli-config.test.ts +++ b/packages/lib/src/__tests__/cli-config.test.ts @@ -1,7 +1,15 @@ import yaml from 'js-yaml' import { loadConfigFile, mergeProfiles, ProfilesByName } from '..' -import { CLIConfig, CLIConfigDescription, loadConfig, resetManagedConfig, seeConfigDocs, setConfigKey } from '../cli-config' +import { + CLIConfig, + CLIConfigDescription, + loadConfig, + resetManagedConfig, + resetManagedConfigKey, + seeConfigDocs, + setConfigKey, +} from '../cli-config' import * as cliConfigModule from '../cli-config' import { readFile, writeFile, yamlExists } from '../io-util' @@ -191,8 +199,8 @@ describe('cli-config', () => { profileName: 'chosenProfile', } - const profiles: ProfilesByName = { mainConfig: { mainConfigProfile: { key: 'value' } } } - const managedProfiles: ProfilesByName = { managedConfig: { managedConfigProfile: { key: 'value' } } } + const profiles: ProfilesByName = { mainConfigProfile: { key: 'value' } } + const managedProfiles: ProfilesByName = { managedConfigProfile: { key: 'value' } } const yamlDumpMock = jest.mocked(yaml.dump) const writeFileMock = jest.mocked(writeFile) @@ -277,6 +285,71 @@ describe('cli-config', () => { expect(mergeProfilesSpy).toHaveBeenCalledWith(profiles, updatedManagedConfig) }) + describe('resetManagedConfigKey', () => { + const deepCopy = (input: T): T => JSON.parse(JSON.stringify(input)) + const makeConfig = (profiles: ProfilesByName, managedProfiles: ProfilesByName): CLIConfig => deepCopy({ + ...description, + profileName: 'defaultProfile', + profiles, + managedProfiles, + mergedProfiles: mergeProfiles(profiles, managedProfiles), + } as CLIConfig) + + const profilesWithKeyToRemove: ProfilesByName = { + profile1: { + keyToRemove: 'remove value', + }, + profile2: { + keyToRemove: 'value to remove', + }, + } + const profilesWithKeysRemoved: ProfilesByName = { + profile1: {}, + profile2: {}, + } + + const predicateMock = jest.fn() + + it('does nothing when key not present', async () => { + const cliConfig = makeConfig(profiles, managedProfiles) + + await expect(resetManagedConfigKey(cliConfig, 'unusedKey', predicateMock)).resolves.not.toThrow() + + expect(cliConfig).toStrictEqual(makeConfig(profiles, managedProfiles)) + expect(predicateMock).toHaveBeenCalledTimes(0) + }) + + it('does not modify user config', async () => { + const cliConfig = makeConfig(profilesWithKeyToRemove, managedProfiles) + + predicateMock.mockReturnValue(true) + await expect(resetManagedConfigKey(cliConfig, 'keyToRemove', predicateMock)).resolves.not.toThrow() + + expect(cliConfig).toStrictEqual(makeConfig(profilesWithKeyToRemove, managedProfiles)) + expect(predicateMock).toHaveBeenCalledTimes(0) + }) + + it('keeps keys that fail predicate', async () => { + const cliConfig = makeConfig(profiles, profilesWithKeyToRemove) + + predicateMock.mockReturnValue(false) + await expect(resetManagedConfigKey(cliConfig, 'keyToRemove', predicateMock)).resolves.not.toThrow() + + expect(cliConfig).toStrictEqual(makeConfig(profiles, profilesWithKeyToRemove)) + expect(predicateMock).toHaveBeenCalledTimes(2) + }) + + it('removes managed keys that match predicate', async () => { + const cliConfig = makeConfig(profiles, profilesWithKeyToRemove) + + predicateMock.mockReturnValue(true) + await expect(resetManagedConfigKey(cliConfig, 'keyToRemove', predicateMock)).resolves.not.toThrow() + + expect(cliConfig).toStrictEqual(makeConfig(profiles, profilesWithKeysRemoved)) + expect(predicateMock).toHaveBeenCalledTimes(2) + }) + }) + test('resetManagedConfig', async () => { const managedProfilesWithProfileToReset: ProfilesByName = { ...managedProfiles, profileToReset: { not: 'empty' } } const cliConfig = { @@ -289,7 +362,7 @@ describe('cli-config', () => { yamlDumpMock.mockReturnValueOnce('yaml output') mergeProfilesSpy.mockReturnValueOnce(updateMergedConfig) - expect(await resetManagedConfig(cliConfig, 'profileToReset')).resolves.not.toThrow + await expect(resetManagedConfig(cliConfig, 'profileToReset')).resolves.not.toThrow() expect(cliConfig.managedProfiles.profileToReset).toBeUndefined() expect(cliConfig.mergedProfiles).toBe(updateMergedConfig) diff --git a/packages/lib/src/cli-config.ts b/packages/lib/src/cli-config.ts index 9ce2bbca4..34f13cbb8 100644 --- a/packages/lib/src/cli-config.ts +++ b/packages/lib/src/cli-config.ts @@ -108,6 +108,24 @@ export const setConfigKey = async (config: CLIConfig, key: string, value: unknow config.mergedProfiles = mergeProfiles(config.profiles, config.managedProfiles) } +/** + * Reset the specified managed config key for for all profiles. The `predicate` is called for each + * value found and the key will only be reset if it returns true. + * + * This can be used to wipe out default values when something is deleted. + */ +export const resetManagedConfigKey = async (config: CLIConfig, key: string, predicate: (value: unknown) => boolean): Promise => { + config.managedProfiles = Object.fromEntries(Object.entries(config.managedProfiles).map(([profileName, profile]) => { + if (key in profile && predicate(profile[key])) { + delete profile[key] + } + return [profileName, profile] + })) + + await writeFile(config.managedConfigFilename, managedConfigHeader + yaml.dump(config.managedProfiles)) + config.mergedProfiles = mergeProfiles(config.profiles, config.managedProfiles) +} + /** * Reset all managed config options for the specified profile. */ diff --git a/packages/testlib/src/index.ts b/packages/testlib/src/index.ts index c4dc5f2f2..6f83d5876 100644 --- a/packages/testlib/src/index.ts +++ b/packages/testlib/src/index.ts @@ -29,6 +29,7 @@ jest.mock('@smartthings/cli-lib', () => { calculateOutputFormat: jest.fn(), writeOutput: jest.fn(), buildOutputFormatter: jest.fn(), + resetManagedConfigKey: jest.fn(), } })