@configdirector/cli 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to `@configdirector/cli` are documented here. The format fol
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.18.0] - 2026-11-03
8
+
9
+ ### Changed
10
+
11
+ - `configdirector configs get`, `evaluate` and `configs create` name an experiment's variations wherever they show one of its values, with the value after the name, as in `40% get control (Start free trial), 60% get treatment-a (Try it free for 14 days)` and `Default targeting rule: control (Start free trial)`. `evaluate` names the variation it served too, as in `treatment-a (Try it free for 14 days), served by rule 2`. A variation without a name, and a config without variations, show the value alone, as before. `--json` prints the Admin API's response as received, which now carries the id of each value's variation.
12
+
7
13
  ## [0.17.0] - 2026-11-01
8
14
 
9
15
  ### Added
package/README.md CHANGED
@@ -211,6 +211,8 @@ production (live)
211
211
  Default targeting rule: false
212
212
  ```
213
213
 
214
+ On an experiment, each value comes after the name of its variation, as in `40% get control (Start free trial), 60% get treatment-a (Try it free for 14 days)`. `evaluate` names the variation it served the same way, as in `treatment-a (Try it free for 14 days), served by rule 2`, and `configs create` names the variation of the default targeting rule value it starts with.
215
+
214
216
  `projects list`, `environments list`, `configs list`, `configs activity` and `segments list` print a header row that names their columns above their lines, such as `KEY TYPE ROLE LIFETIME STATE` for `configs list`, and `--no-headers` leaves it out. An empty list prints only a sentence, such as `No configs found in web-app.`
215
217
 
216
218
  With `--json`, each prints the Admin API's response as received, except `configs list --state`, which prints only the configs in that state. A key no config or segment has exits with 2.
@@ -12,7 +12,7 @@ import { promisify } from "node:util";
12
12
  import { randomUUID } from "node:crypto";
13
13
  import { text } from "node:stream/consumers";
14
14
  //#region package.json
15
- var version = "0.17.0";
15
+ var version = "0.18.0";
16
16
  //#endregion
17
17
  //#region src/CliError.ts
18
18
  var CliError = class extends Error {
@@ -154,14 +154,16 @@ const ConditionSchema = v.variant("kind", [v.object({
154
154
  }), SegmentConditionSchema]);
155
155
  const PercentageSchema = v.object({
156
156
  percentage: v.number(),
157
- value: RuleValueSchema
157
+ value: RuleValueSchema,
158
+ variationId: v.optional(v.string())
158
159
  });
159
160
  const RuleSchema = v.variant("type", [v.variant("target", [v.object({
160
161
  id: v.string(),
161
162
  type: v.literal("conditional"),
162
163
  conditions: v.array(ConditionSchema),
163
164
  target: v.literal("value"),
164
- value: RuleValueSchema
165
+ value: RuleValueSchema,
166
+ variationId: v.optional(v.string())
165
167
  }), v.object({
166
168
  id: v.string(),
167
169
  type: v.literal("conditional"),
@@ -174,6 +176,12 @@ const RuleSchema = v.variant("type", [v.variant("target", [v.object({
174
176
  percentages: v.array(PercentageSchema)
175
177
  })]);
176
178
  //#endregion
179
+ //#region src/admin-api/variations.ts
180
+ const VariationSchema = v.object({
181
+ id: v.string(),
182
+ name: v.nullish(v.string())
183
+ });
184
+ //#endregion
177
185
  //#region src/admin-api/config-with-targeting-rules.ts
178
186
  const TargetingRulesInEnvironmentSchema = v.object({
179
187
  environment: v.object({
@@ -181,7 +189,8 @@ const TargetingRulesInEnvironmentSchema = v.object({
181
189
  live: v.boolean()
182
190
  }),
183
191
  rules: v.array(RuleSchema),
184
- defaultValue: v.string()
192
+ defaultValue: v.string(),
193
+ defaultVariationId: v.optional(v.string())
185
194
  });
186
195
  const NumericBoundSchema = v.object({
187
196
  relation: v.string(),
@@ -239,6 +248,7 @@ const ConfigWithTargetingRulesSchema = v.intersect([v.object({
239
248
  client: v.boolean(),
240
249
  server: v.boolean(),
241
250
  tags: v.array(v.object({ name: v.string() })),
251
+ variations: v.array(VariationSchema),
242
252
  targets: v.array(TargetingRulesInEnvironmentSchema)
243
253
  }), ConfigTypeSchema]);
244
254
  //#endregion
@@ -348,6 +358,7 @@ const ConfigEvaluationSchema = v.object({
348
358
  identifier: v.nullable(v.string())
349
359
  })),
350
360
  value: v.optional(v.string()),
361
+ variationId: v.optional(v.string()),
351
362
  servedBy: v.variant("kind", [v.object({
352
363
  kind: v.literal("rule"),
353
364
  ruleId: v.string()
@@ -355,9 +366,11 @@ const ConfigEvaluationSchema = v.object({
355
366
  rules: v.array(RuleExplanationSchema),
356
367
  targetingRules: v.object({
357
368
  rules: v.array(RuleSchema),
358
- defaultValue: v.string()
369
+ defaultValue: v.string(),
370
+ defaultVariationId: v.optional(v.string())
359
371
  }),
360
- segments: v.record(v.string(), v.object({ name: v.string() }))
372
+ segments: v.record(v.string(), v.object({ name: v.string() })),
373
+ variations: v.optional(v.array(VariationSchema), [])
361
374
  });
362
375
  //#endregion
363
376
  //#region src/admin-api/AdminApiClient.ts
@@ -961,10 +974,12 @@ const describeCondition = (condition, segments) => {
961
974
  case "segment": return `${condition.operator} segment ${segmentNameOf(condition.segmentId, segments)}`;
962
975
  }
963
976
  };
964
- const describePercentages = (percentages) => percentages.map((percentage) => `${formatPercentage(percentage.percentage)}% get ${String(percentage.value)}`).join(", ");
965
- const describeRule = (rule, segments) => {
966
- if (rule.type === "percentage") return describePercentages(rule.percentages);
967
- return `${rule.conditions.map((condition) => describeCondition(condition, segments)).join(" and ")}: ${rule.target === "value" ? String(rule.value) : describePercentages(rule.percentages)}`;
977
+ const valueWithVariationName = (value, variationName) => variationName ? `${variationName} (${String(value)})` : String(value);
978
+ const describeValue = (value, variationId, variations) => valueWithVariationName(value, variations.find((variation) => variation.id === variationId)?.name);
979
+ const describePercentages = (percentages, variations) => percentages.map((percentage) => `${formatPercentage(percentage.percentage)}% get ${describeValue(percentage.value, percentage.variationId, variations)}`).join(", ");
980
+ const describeRule = (rule, segments, variations) => {
981
+ if (rule.type === "percentage") return describePercentages(rule.percentages, variations);
982
+ return `${rule.conditions.map((condition) => describeCondition(condition, segments)).join(" and ")}: ${rule.target === "value" ? describeValue(rule.value, rule.variationId, variations) : describePercentages(rule.percentages, variations)}`;
968
983
  };
969
984
  //#endregion
970
985
  //#region src/configs/config-text.ts
@@ -1001,16 +1016,16 @@ const settingLines = (config) => [
1001
1016
  ...config.tags.length === 0 ? [] : [`Tags: ${config.tags.map((tag) => tag.name).join(", ")}`]
1002
1017
  ];
1003
1018
  const environmentLabel = (environment) => environment.live ? `${environment.slug} (live)` : environment.slug;
1004
- const targetingRuleLines = (targetingRules, segments) => [
1019
+ const targetingRuleLines = (targetingRules, segments, variations) => [
1005
1020
  environmentLabel(targetingRules.environment),
1006
- ...targetingRules.rules.map((rule, index) => ` ${index + 1}. ${describeRule(rule, segments)}`),
1007
- ` Default targeting rule: ${targetingRules.defaultValue}`
1021
+ ...targetingRules.rules.map((rule, index) => ` ${index + 1}. ${describeRule(rule, segments, variations)}`),
1022
+ ` Default targeting rule: ${describeValue(targetingRules.defaultValue, targetingRules.defaultVariationId, variations)}`
1008
1023
  ];
1009
1024
  const configText = (config, requestedKey, segments) => [
1010
1025
  ...oldKeyLines(requestedKey, config.key),
1011
1026
  config.key,
1012
1027
  ...settingLines(config).map((line) => ` ${line}`),
1013
- ...config.targets.flatMap((targetingRules) => ["", ...targetingRuleLines(targetingRules, segments)]),
1028
+ ...config.targets.flatMap((targetingRules) => ["", ...targetingRuleLines(targetingRules, segments, config.variations)]),
1014
1029
  ""
1015
1030
  ].join("\n");
1016
1031
  //#endregion
@@ -1464,6 +1479,10 @@ const getConfig = async (context, [configKey]) => {
1464
1479
  exitCode: ExitCode.success
1465
1480
  };
1466
1481
  };
1482
+ const defaultValueDescription = (newConfig) => {
1483
+ const defaultVariation = newConfig.variations.find((variation) => variation.value === newConfig.defaultValue);
1484
+ return valueWithVariationName(newConfig.defaultValue, defaultVariation?.name);
1485
+ };
1467
1486
  const createdConfigLines = (projectSlug, createdConfig, newConfig) => {
1468
1487
  const settings = [
1469
1488
  createdConfig.type,
@@ -1471,7 +1490,7 @@ const createdConfigLines = (projectSlug, createdConfig, newConfig) => {
1471
1490
  createdConfig.lifetime,
1472
1491
  availabilityDescription(createdConfig)
1473
1492
  ];
1474
- return [`Created ${createdConfig.key} in ${projectSlug}: ${settings.join(", ")}.`, `Default targeting rule in every environment: ${String(newConfig.defaultValue)}`];
1493
+ return [`Created ${createdConfig.key} in ${projectSlug}: ${settings.join(", ")}.`, `Default targeting rule in every environment: ${defaultValueDescription(newConfig)}`];
1475
1494
  };
1476
1495
  const keyTakenLines = (projectSlug, configKey, existingConfig) => [`${projectSlug} already has a config with the key ${configKey}, so no config was created:`, ...alignedColumns([summaryColumnsOf(existingConfig)])];
1477
1496
  const rejectsKey = (error) => error instanceof ApiValidationError && error.fieldErrors.some((fieldError) => fieldError.name === "key");
@@ -1766,6 +1785,7 @@ const contextDescription = (evaluation, contextForm) => {
1766
1785
  const identity = `${evaluatedContext.kind} ${evaluatedContext.identifier}`;
1767
1786
  return contextForm.form === "saved" ? `saved context ${identity}` : identity;
1768
1787
  };
1788
+ const servedValueDescription = (evaluation) => evaluation.value === void 0 ? "no value" : describeValue(evaluation.value, evaluation.variationId, evaluation.variations);
1769
1789
  const servedByDescription = (evaluation) => {
1770
1790
  const { servedBy } = evaluation;
1771
1791
  switch (servedBy.kind) {
@@ -1813,17 +1833,17 @@ const ruleLines = (evaluation) => {
1813
1833
  const rulesById = new Map(evaluation.targetingRules.rules.map((rule) => [rule.id, rule]));
1814
1834
  return alignedColumns(evaluation.rules.map((explanation, index) => {
1815
1835
  const rule = rulesById.get(explanation.ruleId);
1816
- return [`${index + 1}. ${rule === void 0 ? explanation.ruleId : describeRule(rule, evaluation.segments)}`, RULE_OUTCOME_DESCRIPTIONS[explanation.outcome](explanation, conditionsOf(rule), evaluation.segments)];
1836
+ return [`${index + 1}. ${rule === void 0 ? explanation.ruleId : describeRule(rule, evaluation.segments, evaluation.variations)}`, RULE_OUTCOME_DESCRIPTIONS[explanation.outcome](explanation, conditionsOf(rule), evaluation.segments)];
1817
1837
  }));
1818
1838
  };
1819
1839
  const evaluationText = (evaluation, requested) => [
1820
1840
  ...oldKeyLines(requested.configKey, evaluation.key),
1821
1841
  `${evaluation.key} in ${evaluation.environment.name} for ${contextDescription(evaluation, requested.contextForm)}`,
1822
1842
  "",
1823
- `${evaluation.value ?? "no value"}, ${servedByDescription(evaluation)}`,
1843
+ `${servedValueDescription(evaluation)}, ${servedByDescription(evaluation)}`,
1824
1844
  "",
1825
1845
  ...ruleLines(evaluation),
1826
- `Default targeting rule: ${evaluation.targetingRules.defaultValue}`,
1846
+ `Default targeting rule: ${describeValue(evaluation.targetingRules.defaultValue, evaluation.targetingRules.defaultVariationId, evaluation.variations)}`,
1827
1847
  ""
1828
1848
  ].join("\n");
1829
1849
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/cli",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Command line tool for ConfigDirector, the managed configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",