From 585af11a16a0cacb08bd1f3eac4791e23b37d92d Mon Sep 17 00:00:00 2001 From: James Kebinger Date: Wed, 17 Dec 2025 12:39:01 -0600 Subject: [PATCH 1/3] fix: include encrypted value in create --secret payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using `reforge create --secret`, the encrypted value was missing from the API payload. The `mapConfigValueToDto` function wasn't handling the `value` property returned by `makeConfidentialValue`. Changes: - Extract `mapConfigValueToDto` to shared utility (config-value-dto.ts) - Add handling for `configValue.value` (used by encrypted values) - Update both create.ts and set-default.ts to use shared utility - Add payload validation in create test mock for encrypted values - Add `set-default --secret` to interactive menu for consistency - Bump version to 0.0.14 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- package.json | 2 +- src/commands/create.ts | 71 ++---------------------------- src/commands/set-default.ts | 9 ++-- src/interactive-prompt.ts | 7 +++ src/util/config-value-dto.ts | 85 ++++++++++++++++++++++++++++++++++++ test/responses/create.ts | 24 ++++++++++ 6 files changed, 124 insertions(+), 74 deletions(-) create mode 100644 src/util/config-value-dto.ts diff --git a/package.json b/package.json index 763a426..10c7ec7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "packageManager": "yarn@4.11.0", "name": "@reforge-com/cli", - "version": "0.0.13", + "version": "0.0.14", "author": "Jeffrey Chupp @semanticart", "bugs": { "url": "https://github.com/ReforgeHQ/cli/issues" diff --git a/src/commands/create.ts b/src/commands/create.ts index 112c8c2..0d965e3 100644 --- a/src/commands/create.ts +++ b/src/commands/create.ts @@ -7,6 +7,7 @@ import {JsonObj} from '../result.js' import getValue from '../ui/get-value.js' import {TYPE_MAPPING, coerceBool, coerceIntoType} from '../util/coerce.js' import {checkmark} from '../util/color.js' +import {mapConfigValueToDto, mapValueTypeToString} from '../util/config-value-dto.js' import {makeConfidentialValue} from '../util/encryption.js' import secretFlags, {parsedSecretFlags} from '../util/secret-flags.js' @@ -117,13 +118,13 @@ export default class Create extends APICommand { const createConfigRequest = { key: args.name, type: 'config', - valueType: this.mapValueTypeToString(valueType), + valueType: mapValueTypeToString(valueType), sendToClientSdk: false, default: { rules: [ { criteria: [], - value: this.mapConfigValueToDto(configValue, valueType), + value: mapConfigValueToDto(configValue, valueType), }, ], }, @@ -208,70 +209,4 @@ export default class Create extends APICommand { return this.ok(`${checkmark} Created boolean flag: ${key}`, {key, ...response}) } - - private mapConfigValueToDto(configValue: ConfigValue, valueType: ConfigValueType): Record { - const dto: Record = { - type: this.mapValueTypeToString(valueType), - } - - // Handle provided (env-var) values - if (configValue.provided) { - return { - ...dto, - provided: { - source: configValue.provided.source, - lookup: configValue.provided.lookup, - }, - } - } - - // Extract the actual value based on type - let value: unknown - if (configValue.bool !== undefined) { - value = configValue.bool - } else if (configValue.string !== undefined) { - value = configValue.string - } else if (configValue.int !== undefined) { - value = configValue.int - } else if (configValue.double !== undefined) { - value = configValue.double - } else if (configValue.stringList !== undefined) { - value = configValue.stringList.values - } else if (configValue.json !== undefined) { - value = configValue.json - } else if (configValue.duration !== undefined) { - value = configValue.duration - } else if (configValue.intRange !== undefined) { - value = configValue.intRange - } - - dto.value = value - - if (configValue.confidential) { - dto.confidential = true - } - - if (configValue.decryptWith) { - dto.decryptWith = configValue.decryptWith - } - - return dto - } - - private mapValueTypeToString(valueType: ConfigValueType): string { - const mapping: Partial> = { - [ConfigValueType.Bool]: 'bool', - [ConfigValueType.String]: 'string', - [ConfigValueType.Int]: 'int', - [ConfigValueType.Double]: 'double', - [ConfigValueType.StringList]: 'string_list', - [ConfigValueType.Json]: 'json', - [ConfigValueType.LimitDefinition]: 'limit_definition', - [ConfigValueType.Duration]: 'duration', - [ConfigValueType.IntRange]: 'int_range', - [ConfigValueType.Bytes]: 'bytes', - [ConfigValueType.LogLevel]: 'log_level', - } - return mapping[valueType] || 'string' - } } diff --git a/src/commands/set-default.ts b/src/commands/set-default.ts index 0e897a1..55451d3 100644 --- a/src/commands/set-default.ts +++ b/src/commands/set-default.ts @@ -2,12 +2,14 @@ import {Flags} from '@oclif/core' import {ProvidedSource} from '@reforge-com/node' import {APICommand} from '../index.js' +import {ConfigValueType} from '../reforge-common/src/types.js' import {JsonObj} from '../result.js' import getConfirmation, {confirmFlag} from '../ui/get-confirmation.js' import getEnvironment from '../ui/get-environment.js' import getString from '../ui/get-string.js' import autocomplete from '../util/autocomplete.js' import {checkmark} from '../util/color.js' +import {mapConfigValueToDto} from '../util/config-value-dto.js' import {makeConfidentialValue} from '../util/encryption.js' import isInteractive from '../util/is-interactive.js' import nameArg from '../util/name-arg.js' @@ -246,16 +248,13 @@ export default class SetDefault extends APICommand { successMessage = `Successfully changed default to \`${value}\`` if (secret.selected) { - // Handle encrypted values + // Handle encrypted values using shared utility const encryptedValueResult = await makeConfidentialValue(this, value, secret, environmentId) if (!encryptedValueResult.ok) { return this.err(encryptedValueResult.message || 'Failed to encrypt value') } - configValue = { - type: 'string', - ...encryptedValueResult.value, - } + configValue = mapConfigValueToDto(encryptedValueResult.value, ConfigValueType.String) successMessage += ' (encrypted)' } else { // Parse the value based on type and build value object diff --git a/src/interactive-prompt.ts b/src/interactive-prompt.ts index 395af11..7eb0545 100644 --- a/src/interactive-prompt.ts +++ b/src/interactive-prompt.ts @@ -70,6 +70,13 @@ commands.push( id: 'set-default', implicitFlags: ['env-var'], }, + { + command: SetDefault, + description: 'Set/update the default value for an environment with --secret', + displayCommandName: 'set-default --secret', + id: 'set-default', + implicitFlags: ['secret'], + }, { command: Create, description: 'Create a new item in Reforge with --secret', diff --git a/src/util/config-value-dto.ts b/src/util/config-value-dto.ts new file mode 100644 index 0000000..6f53f7f --- /dev/null +++ b/src/util/config-value-dto.ts @@ -0,0 +1,85 @@ +import {ConfigValue, ConfigValueType} from '../reforge-common/src/types.js' + +/** + * Maps a ConfigValue to the DTO format expected by the API. + * Used by both create and set-default commands. + * @param configValue - The config value to convert + * @param valueType - The type of the config value + * @returns The DTO representation of the config value + */ +export function mapConfigValueToDto( + configValue: ConfigValue, + valueType: ConfigValueType, +): Record { + const dto: Record = { + type: mapValueTypeToString(valueType), + } + + // Handle provided (env-var) values + if (configValue.provided) { + return { + ...dto, + provided: { + source: configValue.provided.source, + lookup: configValue.provided.lookup, + }, + } + } + + // Extract the actual value based on type + let value: unknown + if (configValue.bool !== undefined) { + value = configValue.bool + } else if (configValue.string !== undefined) { + value = configValue.string + } else if (configValue.int !== undefined) { + value = configValue.int + } else if (configValue.double !== undefined) { + value = configValue.double + } else if (configValue.stringList !== undefined) { + value = configValue.stringList.values + } else if (configValue.json !== undefined) { + value = configValue.json + } else if (configValue.duration !== undefined) { + value = configValue.duration + } else if (configValue.intRange !== undefined) { + value = configValue.intRange + } else if ((configValue as Record).value !== undefined) { + // Handle encrypted/confidential values where value is directly set + value = (configValue as Record).value + } + + dto.value = value + + if (configValue.confidential) { + dto.confidential = true + } + + if (configValue.decryptWith) { + dto.decryptWith = configValue.decryptWith + } + + return dto +} + +/** + * Converts a ConfigValueType enum to its string representation for the API. + * @param valueType - The config value type enum + * @returns The string representation of the value type + */ +export function mapValueTypeToString(valueType: ConfigValueType): string { + const mapping: Partial> = { + [ConfigValueType.Bool]: 'bool', + [ConfigValueType.String]: 'string', + [ConfigValueType.Int]: 'int', + [ConfigValueType.Double]: 'double', + [ConfigValueType.StringList]: 'string_list', + [ConfigValueType.Json]: 'json', + [ConfigValueType.LimitDefinition]: 'limit_definition', + [ConfigValueType.Duration]: 'duration', + [ConfigValueType.IntRange]: 'int_range', + [ConfigValueType.Bytes]: 'bytes', + [ConfigValueType.LogLevel]: 'log_level', + } + return mapping[valueType] || 'string' +} \ No newline at end of file diff --git a/test/responses/create.ts b/test/responses/create.ts index a5d6e9f..16458ab 100644 --- a/test/responses/create.ts +++ b/test/responses/create.ts @@ -271,6 +271,18 @@ const configsV1Handler = http.post('https://api.goatsofreforge.com/configs/v1', return new Response(JSON.stringify(conflictResponse), {status: 409}) } + // Validate encrypted values have correct structure + const defaultValue = body.default?.rules?.[0]?.value + if (defaultValue?.confidential && defaultValue?.decryptWith) { + // Encrypted values must have type and value fields + if (!defaultValue.type) { + return new Response(JSON.stringify({error: 'Encrypted values must have a type field'}), {status: 400}) + } + if (defaultValue.value === undefined) { + return new Response(JSON.stringify({error: 'Encrypted values must have a value field'}), {status: 400}) + } + } + return new Response(JSON.stringify(successResponse), {status: 200}) }) @@ -281,6 +293,18 @@ const configsV1HandlerProd = http.post('https://api.reforge.com/configs/v1', asy return new Response(JSON.stringify(conflictResponse), {status: 409}) } + // Validate encrypted values have correct structure + const defaultValue = body.default?.rules?.[0]?.value + if (defaultValue?.confidential && defaultValue?.decryptWith) { + // Encrypted values must have type and value fields + if (!defaultValue.type) { + return new Response(JSON.stringify({error: 'Encrypted values must have a type field'}), {status: 400}) + } + if (defaultValue.value === undefined) { + return new Response(JSON.stringify({error: 'Encrypted values must have a value field'}), {status: 400}) + } + } + return new Response(JSON.stringify(successResponse), {status: 200}) }) From 21321c992c3889a06024ab168ac9d28857cf4402 Mon Sep 17 00:00:00 2001 From: James Kebinger Date: Wed, 17 Dec 2025 13:01:32 -0600 Subject: [PATCH 2/3] Prettier this file --- src/util/config-value-dto.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/util/config-value-dto.ts b/src/util/config-value-dto.ts index 6f53f7f..d4ed22c 100644 --- a/src/util/config-value-dto.ts +++ b/src/util/config-value-dto.ts @@ -7,10 +7,7 @@ import {ConfigValue, ConfigValueType} from '../reforge-common/src/types.js' * @param valueType - The type of the config value * @returns The DTO representation of the config value */ -export function mapConfigValueToDto( - configValue: ConfigValue, - valueType: ConfigValueType, -): Record { +export function mapConfigValueToDto(configValue: ConfigValue, valueType: ConfigValueType): Record { const dto: Record = { type: mapValueTypeToString(valueType), } @@ -82,4 +79,4 @@ export function mapValueTypeToString(valueType: ConfigValueType): string { [ConfigValueType.LogLevel]: 'log_level', } return mapping[valueType] || 'string' -} \ No newline at end of file +} From 6006440d24c583c2ce5f1cd4652345e5d2629cd2 Mon Sep 17 00:00:00 2001 From: James Kebinger Date: Wed, 17 Dec 2025 13:03:10 -0600 Subject: [PATCH 3/3] add generated readme update --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7f1ed01..0c9908c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ $ npm install -g @reforge-com/cli $ reforge COMMAND running command... $ reforge (--version) -@reforge-com/cli/0.0.13 darwin-arm64 node-v24.4.1 +@reforge-com/cli/0.0.14 darwin-arm64 node-v24.6.0 $ reforge --help [COMMAND] USAGE $ reforge COMMAND @@ -91,7 +91,7 @@ EXAMPLES $ reforge create my.new.string --type json --value="{\"key\": \"value\"}" ``` -_See code: [src/commands/create.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/create.ts)_ +_See code: [src/commands/create.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/create.ts)_ ## `reforge download` @@ -124,7 +124,7 @@ EXAMPLES $ reforge download --environment=test --sdk-key=YOUR_SDK_KEY ``` -_See code: [src/commands/download.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/download.ts)_ +_See code: [src/commands/download.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/download.ts)_ ## `reforge generate` @@ -194,7 +194,7 @@ EXAMPLES $ reforge generate --targets node-ts -o ./dist # combine with targets ``` -_See code: [src/commands/generate.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/generate.ts)_ +_See code: [src/commands/generate.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/generate.ts)_ ## `reforge generate-new-hex-key` @@ -217,7 +217,7 @@ EXAMPLES $ reforge generate-new-hex-key ``` -_See code: [src/commands/generate-new-hex-key.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/generate-new-hex-key.ts)_ +_See code: [src/commands/generate-new-hex-key.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/generate-new-hex-key.ts)_ ## `reforge get [NAME]` @@ -250,7 +250,7 @@ EXAMPLES $ reforge get my.config.name --environment=production ``` -_See code: [src/commands/get.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/get.ts)_ +_See code: [src/commands/get.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/get.ts)_ ## `reforge info [NAME]` @@ -281,7 +281,7 @@ EXAMPLES $ reforge info my.config.name ``` -_See code: [src/commands/info.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/info.ts)_ +_See code: [src/commands/info.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/info.ts)_ ## `reforge interactive` @@ -299,7 +299,7 @@ EXAMPLES $ reforge ``` -_See code: [src/commands/interactive.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/interactive.ts)_ +_See code: [src/commands/interactive.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/interactive.ts)_ ## `reforge list` @@ -336,7 +336,7 @@ EXAMPLES $ reforge list --feature-flags ``` -_See code: [src/commands/list.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/list.ts)_ +_See code: [src/commands/list.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/list.ts)_ ## `reforge login` @@ -364,7 +364,7 @@ EXAMPLES $ reforge login --profile myprofile ``` -_See code: [src/commands/login.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/login.ts)_ +_See code: [src/commands/login.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/login.ts)_ ## `reforge logout` @@ -387,7 +387,7 @@ EXAMPLES $ reforge logout ``` -_See code: [src/commands/logout.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/logout.ts)_ +_See code: [src/commands/logout.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/logout.ts)_ ## `reforge mcp` @@ -420,7 +420,7 @@ EXAMPLES $ reforge mcp --url http://local-launch.goatsofreforge.com:3003/api/v1/mcp ``` -_See code: [src/commands/mcp.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/mcp.ts)_ +_See code: [src/commands/mcp.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/mcp.ts)_ ## `reforge override [NAME]` @@ -459,7 +459,7 @@ EXAMPLES $ reforge override my.double.config --value=3.14159 ``` -_See code: [src/commands/override.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/override.ts)_ +_See code: [src/commands/override.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/override.ts)_ ## `reforge profile` @@ -482,7 +482,7 @@ EXAMPLES $ reforge profile ``` -_See code: [src/commands/profile.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/profile.ts)_ +_See code: [src/commands/profile.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/profile.ts)_ ## `reforge schema NAME` @@ -516,7 +516,7 @@ EXAMPLES $ reforge schema my-schema --get ``` -_See code: [src/commands/schema.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/schema.ts)_ +_See code: [src/commands/schema.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/schema.ts)_ ## `reforge serve DATA-FILE` @@ -552,7 +552,7 @@ EXAMPLES $ reforge serve ./reforge.test.588.config.json --port=3099 ``` -_See code: [src/commands/serve.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/serve.ts)_ +_See code: [src/commands/serve.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/serve.ts)_ ## `reforge set-default [NAME]` @@ -596,7 +596,7 @@ EXAMPLES $ reforge set-default my.config.name --env-var=MY_ENV_VAR_NAME --environment=production ``` -_See code: [src/commands/set-default.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/set-default.ts)_ +_See code: [src/commands/set-default.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/set-default.ts)_ ## `reforge whoami` @@ -619,7 +619,7 @@ EXAMPLES $ reforge whoami ``` -_See code: [src/commands/whoami.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/whoami.ts)_ +_See code: [src/commands/whoami.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/whoami.ts)_ ## `reforge workspace` @@ -642,7 +642,7 @@ EXAMPLES $ reforge workspace ``` -_See code: [src/commands/workspace.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.13/src/commands/workspace.ts)_ +_See code: [src/commands/workspace.ts](https://github.com/ReforgeHQ/cli/blob/v0.0.14/src/commands/workspace.ts)_ ## Local Development