@configdirector/cli 0.14.0 → 0.15.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,16 @@ All notable changes to `@configdirector/cli` are documented here. The format fol
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.15.0] - 2026-10-28
8
+
9
+ ### Added
10
+
11
+ - `--no-headers` on `configdirector projects list`, `environments list`, `configs list`, `configs activity`, `segments list` and `repositories list` leaves out the header row and prints the lines alone, as earlier versions did. A script that reads these lines can add `--no-headers`, or read `--json`, which has not changed.
12
+
13
+ ### Changed
14
+
15
+ - `configdirector projects list`, `environments list`, `configs list`, `configs activity`, `segments list` and `repositories list` print a header row that names their columns, such as `KEY TYPE ROLE LIFETIME STATE` above the configs. The columns line up under it, so a header longer than every value in its column widens that column. An empty list still prints only its sentence, such as `No configs found in web-app.`
16
+
7
17
  ## [0.14.0] - 2026-10-27
8
18
 
9
19
  ### Added
package/README.md CHANGED
@@ -88,16 +88,17 @@ A project holds at most 50 scanned repositories, and one upload holds at most 10
88
88
  configdirector repositories list
89
89
  ```
90
90
 
91
- `repositories list` lists the repositories whose scan was uploaded to the project, one per line, with the commit, when it was scanned in UTC, and how many references and dynamic references it holds, and ends with how many scanned repositories the project has out of the 50 it can hold:
91
+ `repositories list` lists the repositories whose scan was uploaded to the project, one per line under a header row, with the commit, when it was scanned in UTC, and how many references and dynamic references it holds, and ends with how many scanned repositories the project has out of the 50 it can hold:
92
92
 
93
93
  ```txt
94
+ REPOSITORY COMMIT SCANNED REFERENCES DYNAMIC REFERENCES
94
95
  ConfigDirector/cli 4d40f50 2026-10-17 10:00 UTC 12 references 1 dynamic reference
95
96
  acme/storefront 9f3e2a1 2026-10-16 09:12 UTC 1 reference 0 dynamic references
96
97
 
97
98
  web-app has 2 of the 50 scanned repositories a project can hold.
98
99
  ```
99
100
 
100
- A repository uploaded without a commit shows `no commit`. With `--json`, it prints the Admin API's response as received, which also holds each repository's remote URL and how many files its scan read. `repositories list` needs `projects:read` and `code-references:read`.
101
+ A repository uploaded without a commit shows `no commit`, and `--no-headers` leaves out the header row. With `--json`, it prints the Admin API's response as received, which also holds each repository's remote URL and how many files its scan read. `repositories list` needs `projects:read` and `code-references:read`.
101
102
 
102
103
  ```bash
103
104
  configdirector repositories remove acme/storefront
@@ -210,6 +211,8 @@ production (live)
210
211
  Default targeting rule: false
211
212
  ```
212
213
 
214
+ `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
+
213
216
  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.
214
217
 
215
218
  `projects list` needs `projects:read`. The others need `projects:read` to find the project, and `environments list` needs `environments:read`, `configs list`, `configs get` and `configs activity` need `configs:read`, plus `tags:read` for `--tag`, and `segments list` and `segments get` need `segments:read`. Without `segments:read`, `configs get` shows a segment condition's segment ID instead of its name; without `environments:read`, `segments get` names an override's environment by its ID instead of its slug.
@@ -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.14.0";
15
+ var version = "0.15.0";
16
16
  //#endregion
17
17
  //#region src/CliError.ts
18
18
  var CliError = class extends Error {
@@ -910,6 +910,7 @@ const getApiToken = async (context) => {
910
910
  const COLUMN_GAP = " ";
911
911
  const widestCellOf = (rows, column) => Math.max(0, ...rows.map((row) => row[column]?.length ?? 0));
912
912
  const alignedColumns = (rows) => rows.map((row) => row.map((cell, column) => column === row.length - 1 ? cell : cell.padEnd(widestCellOf(rows, column))).join(COLUMN_GAP));
913
+ const alignedListing = (headerRow, rows, options) => alignedColumns(options.headers ? [headerRow, ...rows] : rows);
913
914
  //#endregion
914
915
  //#region src/old-key-lines.ts
915
916
  const oldKeyLines = (requestedKey, currentKey) => requestedKey === currentKey ? [] : [`${requestedKey} is an old key of ${currentKey}.`];
@@ -1289,6 +1290,13 @@ const tagFilterOf = async (client, project, options) => {
1289
1290
  };
1290
1291
  };
1291
1292
  const configsInState = (listedConfigs, state) => state === void 0 ? listedConfigs : listedConfigs.filter((listedConfig) => listedConfig.config.state === state);
1293
+ const CONFIG_SUMMARY_HEADER_ROW = [
1294
+ "KEY",
1295
+ "TYPE",
1296
+ "ROLE",
1297
+ "LIFETIME",
1298
+ "STATE"
1299
+ ];
1292
1300
  const summaryColumnsOf = (config) => [
1293
1301
  config.key,
1294
1302
  config.type,
@@ -1296,13 +1304,13 @@ const summaryColumnsOf = (config) => [
1296
1304
  config.lifetime,
1297
1305
  config.state
1298
1306
  ];
1299
- const configLines = (projectSlug, configs) => configs.length === 0 ? [`No configs found in ${projectSlug}.`] : alignedColumns(configs.map(summaryColumnsOf));
1307
+ const configLines = (projectSlug, configs, options) => configs.length === 0 ? [`No configs found in ${projectSlug}.`] : alignedListing(CONFIG_SUMMARY_HEADER_ROW, configs.map(summaryColumnsOf), options);
1300
1308
  const listConfigs = async (context, _commandArguments, options) => {
1301
1309
  const { client, project } = await connectToLinkedProject(context);
1302
1310
  const listedConfigs = configsInState(await client.listConfigs(project.id, await tagFilterOf(client, project, options)), options.state);
1303
1311
  return {
1304
1312
  json: listedConfigs.map((listedConfig) => listedConfig.receivedConfig),
1305
- text: [...configLines(project.slug, listedConfigs.map((listedConfig) => listedConfig.config)), ""].join("\n"),
1313
+ text: [...configLines(project.slug, listedConfigs.map((listedConfig) => listedConfig.config), options), ""].join("\n"),
1306
1314
  exitCode: ExitCode.success
1307
1315
  };
1308
1316
  };
@@ -1450,36 +1458,47 @@ const archiveConfig = async (context, [configKey]) => {
1450
1458
  };
1451
1459
  };
1452
1460
  const sdkDescription = (evaluation) => [evaluation.sdkName ?? "unknown SDK", ...evaluation.sdkVersion === null ? [] : [evaluation.sdkVersion]].join(" ");
1453
- const activityLines = (configKey, activity) => activity.latestEvaluations.length === 0 ? [`No SDK evaluations of ${configKey} found.`] : alignedColumns(activity.latestEvaluations.map((evaluation) => [
1461
+ const LATEST_EVALUATION_HEADER_ROW = [
1462
+ "ENVIRONMENT",
1463
+ "SDK KEY",
1464
+ "SDK",
1465
+ "LAST EVALUATED"
1466
+ ];
1467
+ const activityLines = (configKey, activity, options) => activity.latestEvaluations.length === 0 ? [`No SDK evaluations of ${configKey} found.`] : alignedListing(LATEST_EVALUATION_HEADER_ROW, activity.latestEvaluations.map((evaluation) => [
1454
1468
  evaluation.environment.slug,
1455
1469
  evaluation.source,
1456
1470
  sdkDescription(evaluation),
1457
1471
  utcMinute(evaluation.time)
1458
- ]));
1459
- const showConfigActivity = async (context, [configKey]) => {
1472
+ ]), options);
1473
+ const showConfigActivity = async (context, [configKey], options) => {
1460
1474
  const { client, project } = await connectToLinkedProject(context);
1461
1475
  const receivedActivity = await client.getConfigActivity(project.id, configKey);
1462
1476
  if (receivedActivity === void 0) throw new CliError(`No config in ${project.slug} has the key "${configKey}".`);
1463
1477
  return {
1464
1478
  json: receivedActivity.responseBody,
1465
- text: [...activityLines(configKey, receivedActivity.parsedBody), ""].join("\n"),
1479
+ text: [...activityLines(configKey, receivedActivity.parsedBody, options), ""].join("\n"),
1466
1480
  exitCode: ExitCode.success
1467
1481
  };
1468
1482
  };
1469
1483
  //#endregion
1470
1484
  //#region src/commands/environments.ts
1485
+ const ENVIRONMENT_HEADER_ROW = [
1486
+ "SLUG",
1487
+ "NAME",
1488
+ "LIVE"
1489
+ ];
1471
1490
  const environmentColumns = (environment) => environment.live ? [
1472
1491
  environment.slug,
1473
1492
  environment.name,
1474
1493
  "live"
1475
1494
  ] : [environment.slug, environment.name];
1476
- const environmentLines = (projectSlug, environments) => environments.length === 0 ? [`No environments found in ${projectSlug}.`] : alignedColumns(environments.map(environmentColumns));
1477
- const listEnvironments = async (context) => {
1495
+ const environmentLines = (projectSlug, environments, options) => environments.length === 0 ? [`No environments found in ${projectSlug}.`] : alignedListing(ENVIRONMENT_HEADER_ROW, environments.map(environmentColumns), options);
1496
+ const listEnvironments = async (context, _commandArguments, options) => {
1478
1497
  const { client, project } = await connectToLinkedProject(context);
1479
1498
  const { parsedBody: environments, responseBody } = await client.listEnvironments(project.id);
1480
1499
  return {
1481
1500
  json: responseBody,
1482
- text: [...environmentLines(project.slug, environments), ""].join("\n"),
1501
+ text: [...environmentLines(project.slug, environments, options), ""].join("\n"),
1483
1502
  exitCode: ExitCode.success
1484
1503
  };
1485
1504
  };
@@ -1875,15 +1894,16 @@ const logout = async (context) => {
1875
1894
  };
1876
1895
  //#endregion
1877
1896
  //#region src/commands/projects.ts
1878
- const projectLines = (projects) => projects.length === 0 ? ["No projects found."] : alignedColumns(projects.map((project) => [project.slug, project.name]));
1879
- const listProjects = async (context) => {
1897
+ const PROJECT_HEADER_ROW = ["SLUG", "NAME"];
1898
+ const projectLines = (projects, options) => projects.length === 0 ? ["No projects found."] : alignedListing(PROJECT_HEADER_ROW, projects.map((project) => [project.slug, project.name]), options);
1899
+ const listProjects = async (context, _commandArguments, options) => {
1880
1900
  const { parsedBody: projects, responseBody } = await new AdminApiClient({
1881
1901
  baseUrl: context.baseUrl,
1882
1902
  token: (await configuredApiToken(context)).token
1883
1903
  }).listProjects();
1884
1904
  return {
1885
1905
  json: responseBody,
1886
- text: [...projectLines(projects), ""].join("\n"),
1906
+ text: [...projectLines(projects, options), ""].join("\n"),
1887
1907
  exitCode: ExitCode.success
1888
1908
  };
1889
1909
  };
@@ -1895,6 +1915,13 @@ const shortCommit = (commit) => commit.slice(0, SHORT_COMMIT_LENGTH);
1895
1915
  //#region src/commands/repositories.ts
1896
1916
  const MAX_SCANNED_REPOSITORIES_PER_PROJECT = 50;
1897
1917
  const commitDescription = (commit) => commit === null ? "no commit" : shortCommit(commit);
1918
+ const SCANNED_REPOSITORY_HEADER_ROW = [
1919
+ "REPOSITORY",
1920
+ "COMMIT",
1921
+ "SCANNED",
1922
+ "REFERENCES",
1923
+ "DYNAMIC REFERENCES"
1924
+ ];
1898
1925
  const scannedRepositoryColumnsOf = (repository) => [
1899
1926
  repository.name,
1900
1927
  commitDescription(repository.commit),
@@ -1902,17 +1929,17 @@ const scannedRepositoryColumnsOf = (repository) => [
1902
1929
  `${repository.referenceCount} ${plural(repository.referenceCount, "reference", "references")}`,
1903
1930
  `${repository.dynamicReferenceCount} ${plural(repository.dynamicReferenceCount, "dynamic reference", "dynamic references")}`
1904
1931
  ];
1905
- const scannedRepositoryLines = (projectSlug, repositories) => repositories.length === 0 ? [`No scanned repositories found in ${projectSlug}. Run configdirector scan --upload in a repository to upload its code references.`] : [
1906
- ...alignedColumns(repositories.map(scannedRepositoryColumnsOf)),
1932
+ const scannedRepositoryLines = (projectSlug, repositories, options) => repositories.length === 0 ? [`No scanned repositories found in ${projectSlug}. Run configdirector scan --upload in a repository to upload its code references.`] : [
1933
+ ...alignedListing(SCANNED_REPOSITORY_HEADER_ROW, repositories.map(scannedRepositoryColumnsOf), options),
1907
1934
  "",
1908
1935
  `${projectSlug} has ${repositories.length} of the ${MAX_SCANNED_REPOSITORIES_PER_PROJECT} scanned repositories a project can hold.`
1909
1936
  ];
1910
- const listScannedRepositories = async (context) => {
1937
+ const listScannedRepositories = async (context, _commandArguments, options) => {
1911
1938
  const { client, project } = await connectToLinkedProject(context);
1912
1939
  const { parsedBody: repositories, responseBody } = await client.listScannedRepositories(project.id);
1913
1940
  return {
1914
1941
  json: responseBody,
1915
- text: [...scannedRepositoryLines(project.slug, repositories), ""].join("\n"),
1942
+ text: [...scannedRepositoryLines(project.slug, repositories, options), ""].join("\n"),
1916
1943
  exitCode: ExitCode.success
1917
1944
  };
1918
1945
  };
@@ -4601,17 +4628,22 @@ const segmentText = (segment, environmentSlugs) => [
4601
4628
  //#endregion
4602
4629
  //#region src/commands/segments.ts
4603
4630
  const usageDescription = (segment) => `used by ${segment.usages.length} ${plural(segment.usages.length, "config", "configs")}`;
4604
- const segmentLines = (projectSlug, segments) => segments.length === 0 ? [`No segments found in ${projectSlug}.`] : alignedColumns(segments.map((segment) => [
4631
+ const SEGMENT_HEADER_ROW = [
4632
+ "KEY",
4633
+ "NAME",
4634
+ "USED BY"
4635
+ ];
4636
+ const segmentLines = (projectSlug, segments, options) => segments.length === 0 ? [`No segments found in ${projectSlug}.`] : alignedListing(SEGMENT_HEADER_ROW, segments.map((segment) => [
4605
4637
  segment.key,
4606
4638
  segment.name,
4607
4639
  usageDescription(segment)
4608
- ]));
4609
- const listSegments = async (context) => {
4640
+ ]), options);
4641
+ const listSegments = async (context, _commandArguments, options) => {
4610
4642
  const { client, project } = await connectToLinkedProject(context);
4611
4643
  const { parsedBody: segments, responseBody } = await client.listSegments(project.id);
4612
4644
  return {
4613
4645
  json: responseBody,
4614
- text: [...segmentLines(project.slug, segments), ""].join("\n"),
4646
+ text: [...segmentLines(project.slug, segments, options), ""].join("\n"),
4615
4647
  exitCode: ExitCode.success
4616
4648
  };
4617
4649
  };
@@ -4958,6 +4990,7 @@ const validate = async (context, [path]) => {
4958
4990
  //#endregion
4959
4991
  //#region src/run.ts
4960
4992
  const DEFAULT_BASE_URL = "https://api.configdirector.com";
4993
+ const NO_HEADERS_DESCRIPTION = "leave out the header row";
4961
4994
  const SEGMENT_FILE_DESCRIPTION = "a JSON file holding the segment as the Admin API takes it, or - to read it from stdin (required)";
4962
4995
  const withoutTrailingSlashes = (url) => {
4963
4996
  let trimmedUrl = url;
@@ -5003,16 +5036,16 @@ const run = async (invocation) => {
5003
5036
  program.command("validate").argument("[path]", "directory or file to check (default: the current directory)").description("check every config key the code reads against the project's configs, exiting 1 when one is wrong").action(commandAction(validate));
5004
5037
  program.command("evaluate").argument("<key>", "the config's key").description("show the value a context gets for a config in an environment, and why").requiredOption("--environment <slug>", "slug of the environment whose targeting rules to evaluate (required)").option("--context-kind <kind>", "context kind of the context to evaluate for (default: user)").option("--context-id <identifier>", "identifier of the context").option("--context-name <name>", "name of the context").option("--context-trait <name=value>", "a trait of the context, as a string, or name:=<json> for a number, boolean, array or object; repeatable", repeatedOption).option("--context-json <json>", "the whole context as a JSON object, or @<file> to read it from a file").option("--saved-context <kind:identifier>", "a context ConfigDirector saved from SDK evaluations in the environment, such as user:123").addHelpText("after", "\nGive the context with the --context-* options, with --context-json, or with --saved-context. Without any of them, the config is evaluated for an empty context, as an SDK given none would.").action(commandAction(evaluate));
5005
5038
  program.command("api-token").description("describe the API token a command here would use").command("get").description("show the token's organization, name, permissions and expiration, and where it came from; never the token itself").action(commandAction(getApiToken));
5006
- program.command("projects").description("list the projects of the API token's organization").command("list").description("list the organization's projects, whatever project is selected").action(commandAction(listProjects));
5007
- program.command("environments").description("list the project's environments").command("list").description("list the project's environments with their slugs, marking the live ones").action(commandAction(listEnvironments));
5039
+ program.command("projects").description("list the projects of the API token's organization").command("list").description("list the organization's projects, whatever project is selected").option("--no-headers", NO_HEADERS_DESCRIPTION).action(commandAction(listProjects));
5040
+ program.command("environments").description("list the project's environments").command("list").description("list the project's environments with their slugs, marking the live ones").option("--no-headers", NO_HEADERS_DESCRIPTION).action(commandAction(listEnvironments));
5008
5041
  const configsCommand = program.command("configs").description("list, show, create and archive a project's configs");
5009
- configsCommand.command("list").description("list the project's configs with their type, role, lifetime and lifecycle state").option("--tag <name>", "list only the configs that have this tag; repeatable, for configs with any of them", repeatedOption).addOption(new Option("--untagged", "list only the configs without tags").conflicts("tag")).addOption(new Option("--state <state>", "list only the configs in this lifecycle state").choices(LIFECYCLE_STATES)).action(commandAction(listConfigs));
5042
+ configsCommand.command("list").description("list the project's configs with their type, role, lifetime and lifecycle state").option("--tag <name>", "list only the configs that have this tag; repeatable, for configs with any of them", repeatedOption).addOption(new Option("--untagged", "list only the configs without tags").conflicts("tag")).addOption(new Option("--state <state>", "list only the configs in this lifecycle state").choices(LIFECYCLE_STATES)).option("--no-headers", NO_HEADERS_DESCRIPTION).action(commandAction(listConfigs));
5010
5043
  configsCommand.command("get").argument("<key>", "the config's key, or an old key of it").description("show a config's settings and its targeting rules in every environment").action(commandAction(getConfig));
5011
- configsCommand.command("activity").argument("<key>", "the config's key, or an old key of it").description("show when SDKs last evaluated a config, per environment, SDK key kind, SDK name and SDK version").action(commandAction(showConfigActivity));
5044
+ configsCommand.command("activity").argument("<key>", "the config's key, or an old key of it").description("show when SDKs last evaluated a config, per environment, SDK key kind, SDK name and SDK version").option("--no-headers", NO_HEADERS_DESCRIPTION).action(commandAction(showConfigActivity));
5012
5045
  configsCommand.command("create").description("create a config from options that mirror the Admin API's fields").requiredOption("--key <key>", "the config's key (required)").option("--description <text>", "a description shown with the config in the dashboard").addOption(new Option("--type <type>", "the type of the config's values (required)").choices(CONFIG_TYPES).makeOptionMandatory()).addOption(new Option("--role <role>", "what the config is for (required)").choices(CONFIG_ROLES).makeOptionMandatory()).addOption(new Option("--lifetime <lifetime>", "whether the config is expected to be removed (required)").choices(CONFIG_LIFETIMES).makeOptionMandatory()).requiredOption("--default-value <value>", "the default targeting rule value every environment starts with, in the type given by --type; not the in-code default value an application passes to the SDK (required)").option("--client-only", "serve the config to client SDKs only (default: client and server SDKs)").addOption(new Option("--server-only", "serve the config to server SDKs only (default: client and server SDKs)").conflicts("clientOnly")).option("--tag <name>", "assign the project's tag with this name; repeatable", repeatedOption).option("--new-tag <name>", "create a tag with this name in the project's tag catalog and assign it; repeatable", repeatedOption).option("--variation <name=value>", "a variation of an experiment, its value in the type given by --type; repeatable", repeatedOption).addOption(new Option("--min <number>", "the smallest value allowed, included").helpGroup(typeConstraintHelpGroup("min"))).addOption(new Option("--max <number>", "the largest value allowed, included").helpGroup(typeConstraintHelpGroup("max"))).addOption(new Option("--exclusive-min <number>", "values must be greater than this number").conflicts("min").helpGroup(typeConstraintHelpGroup("exclusiveMin"))).addOption(new Option("--exclusive-max <number>", "values must be less than this number").conflicts("max").helpGroup(typeConstraintHelpGroup("exclusiveMax"))).addOption(new Option("--unit <unit>", "the unit of the number the SDKs receive (required)").choices(TIMESPAN_UNITS).helpGroup(typeConstraintHelpGroup("unit"))).addOption(new Option("--allow-fractions", "allow values with a fraction of the unit").helpGroup(typeConstraintHelpGroup("allowFractions"))).addOption(new Option("--allowed-value <value>", "a value the config can take; repeatable").argParser(repeatedOption).helpGroup(typeConstraintHelpGroup("allowedValue"))).addOption(new Option("--enum-value-type <type>", "whether the allowed values are strings or numbers (default: string)").choices(ENUM_VALUE_TYPES).helpGroup(typeConstraintHelpGroup("enumValueType"))).addOption(new Option("--https-only", "allow only HTTPS URLs").helpGroup(typeConstraintHelpGroup("httpsOnly"))).addOption(new Option("--json-schema <file>", "a file holding the JSON Schema every value must match").helpGroup(typeConstraintHelpGroup("jsonSchema"))).action(commandAction(createConfig));
5013
5046
  configsCommand.command("archive").argument("<key>", "the config's key, or an old key of it").description("archive a config, so SDKs stop receiving it; the dashboard can restore it for 30 days").addHelpText("after", "\nThe config is archived with no confirmation and no check of whether the code still reads its key, so run configdirector scan first. Applications that still read it get their in-code default value.").action(commandAction(archiveConfig));
5014
5047
  const segmentsCommand = program.command("segments").description("list, show, create, update and delete a project's segments");
5015
- segmentsCommand.command("list").description("list the project's segments with how many configs use each").action(commandAction(listSegments));
5048
+ segmentsCommand.command("list").description("list the project's segments with how many configs use each").option("--no-headers", NO_HEADERS_DESCRIPTION).action(commandAction(listSegments));
5016
5049
  segmentsCommand.command("get").argument("<key>", "the segment's key").description("show a segment's condition groups and its environment overrides").action(commandAction(getSegment));
5017
5050
  segmentsCommand.command("create").description("create a segment from a JSON file").requiredOption("--segment <file>", SEGMENT_FILE_DESCRIPTION).addHelpText("after", "\nThe file holds {\"key\": ..., \"name\": ..., \"groups\": [...], \"overrides\": {...}}, like the output of segments get --json, so you can edit that output and send it. groups lists the condition groups, and a context is in the segment when every condition of any one group matches. overrides is optional: it maps an environment's id, which environments list --json shows, to the condition groups used there instead. A condition without an id gets a new one.").action(commandAction(createSegment));
5018
5051
  segmentsCommand.command("update").argument("<key>", "the segment's key").description("replace a segment's key, name and condition groups with a JSON file, and its environment overrides when the file has them").requiredOption("--segment <file>", SEGMENT_FILE_DESCRIPTION).addHelpText("after", "\nThe file takes the same form as for segments create, and the segment keeps its id whatever id the file holds. Its key, name and condition groups are replaced, so a different key renames it. Without overrides in the file, the environment overrides stay as they are; with \"overrides\": {} they are all removed, and a map leaves exactly those environments overridden. A condition without an id gets a new one.").action(commandAction(updateSegment));
@@ -5021,7 +5054,7 @@ const run = async (invocation) => {
5021
5054
  targetingRulesCommand.command("set").argument("<key>", "the config's key, or an old key of it").description("replace a config's targeting rules in one environment with the rules of a JSON file").requiredOption("--environment <slug>", "slug of the environment whose targeting rules to replace (required)").requiredOption("--rules <file>", "a JSON file holding defaultValue and rules as the Admin API takes them, or - to read it from stdin (required)").addHelpText("after", "\nThe file holds {\"defaultValue\": ..., \"rules\": [...]}, like an entry of targets in configs get --json, so you can edit that output and write it back. Every rule of the environment is replaced. A rule, condition or percentage without an id gets a new one. configdirector targeting-rules reference lists the condition kinds, attributes and operators.").action(commandAction(setTargetingRules));
5022
5055
  targetingRulesCommand.command("reference").description("show the format targeting rules take: the condition kinds, the attributes a condition can test and the operators").action(commandAction(showTargetingRulesReference));
5023
5056
  const repositoriesCommand = program.command("repositories").description("list a project's scanned repositories and remove one");
5024
- repositoriesCommand.command("list").description("list the repositories whose scan was uploaded to the project, with their commit, scan time and reference counts").action(commandAction(listScannedRepositories));
5057
+ repositoriesCommand.command("list").description("list the repositories whose scan was uploaded to the project, with their commit, scan time and reference counts").option("--no-headers", NO_HEADERS_DESCRIPTION).action(commandAction(listScannedRepositories));
5025
5058
  repositoriesCommand.command("remove").argument("<name>", "the repository's name, as repositories list shows it, such as ConfigDirector/cli").description("remove a scanned repository and its code references from the project").addHelpText("after", "\nThe repository is removed with no confirmation, and the dashboard stops showing its code references. The next scan --upload from it adds it back.").action(commandAction(removeScannedRepository));
5026
5059
  try {
5027
5060
  await program.parseAsync(invocation.commandLineArguments, { from: "user" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Command line tool for ConfigDirector, the managed configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",