@contrail/cli 3.6.0-alpha-8-alpha.e4ae6bd → 3.6.0-alpha-8-alpha.7d24daa

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/README.md CHANGED
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
17
17
  $ contrail COMMAND
18
18
  running command...
19
19
  $ contrail (--version)
20
- @contrail/cli/3.6.0-alpha-8-alpha.e4ae6bd linux-x64 node-v24.21.0
20
+ @contrail/cli/3.6.0-alpha-8-alpha.7d24daa linux-x64 node-v24.21.0
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -543,12 +543,12 @@ EXAMPLES
543
543
 
544
544
  ## `contrail entities create PATH`
545
545
 
546
- (BETA) Create entities
546
+ Create entities
547
547
 
548
548
  ```
549
549
  USAGE
550
550
  $ contrail entities create PATH [-l error|warn|info|debug] [-e true|false] [-n <value>] [-d] [-i <value>] [-o
551
- <value>] [-g] [-r] [-S]
551
+ <value>] [-g] [-r] [-S] [--suffix <value>]
552
552
 
553
553
  ARGUMENTS
554
554
  PATH Path of the entity type to create
@@ -568,9 +568,11 @@ FLAGS
568
568
  -n, --count=<value> Number of random entities to create
569
569
  -o, --outputFile=<value> File to save the output (optional)
570
570
  -r, --readableIds If true, uses readable IDs for the created entities
571
+ --suffix=<value> Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets
572
+ /<entities>/synchronous)
571
573
 
572
574
  DESCRIPTION
573
- (BETA) Create entities
575
+ Create entities
574
576
 
575
577
  EXAMPLES
576
578
  $ contrail entities create color -g -n=10 # creates 10 random colors
@@ -594,11 +596,12 @@ EXAMPLES
594
596
 
595
597
  ## `contrail entities delete ENTITYNAME [IDS]`
596
598
 
597
- (BETA) Delete entities by id
599
+ Delete entities by id
598
600
 
599
601
  ```
600
602
  USAGE
601
- $ contrail entities delete ENTITYNAME [IDS] [-l error|warn|info|debug] [-e true|false] [-d] [-i <value>]
603
+ $ contrail entities delete ENTITYNAME [IDS] [-l error|warn|info|debug] [-e true|false] [-d] [-i <value>] [--suffix
604
+ <value>]
602
605
 
603
606
  ARGUMENTS
604
607
  ENTITYNAME The name of the entity type to delete
@@ -612,9 +615,11 @@ FLAGS
612
615
  entity in the file.
613
616
  -l, --logLevel=<option> The Log Level
614
617
  <options: error|warn|info|debug>
618
+ --suffix=<value> Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets
619
+ /<entities>/synchronous)
615
620
 
616
621
  DESCRIPTION
617
- (BETA) Delete entities by id
622
+ Delete entities by id
618
623
 
619
624
  EXAMPLES
620
625
  $ contrail entities delete color 123
@@ -626,12 +631,12 @@ EXAMPLES
626
631
 
627
632
  ## `contrail entities get TYPEPATH [IDS]`
628
633
 
629
- (BETA) Get an entity
634
+ Get an entity
630
635
 
631
636
  ```
632
637
  USAGE
633
638
  $ contrail entities get TYPEPATH [IDS] [-l error|warn|info|debug] [-e true|false] [-o <value>] [-p <value>] [-P]
634
- [-s <value>] [-S] [-t <value>] [-r <value>]
639
+ [-s <value>] [-S] [-t <value>] [-r <value>] [--suffix <value>]
635
640
 
636
641
  ARGUMENTS
637
642
  TYPEPATH The name of the entity type to fetch
@@ -649,9 +654,11 @@ FLAGS
649
654
  -r, --relations=<value> Comma-separated list of relations to include in the output
650
655
  -s, --skip=<value> Number of entities to skip (used with paginated fetch)
651
656
  -t, --take=<value> Number of entities to fetch in a single request (default is 100)
657
+ --suffix=<value> Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets
658
+ /<entities>/synchronous)
652
659
 
653
660
  DESCRIPTION
654
- (BETA) Get an entity
661
+ Get an entity
655
662
 
656
663
  EXAMPLES
657
664
  $ contrail entities get color 123
@@ -669,11 +676,12 @@ EXAMPLES
669
676
 
670
677
  ## `contrail entities update ENTITYNAME [IDS]`
671
678
 
672
- (BETA) Update an entity
679
+ Update an entity
673
680
 
674
681
  ```
675
682
  USAGE
676
683
  $ contrail entities update ENTITYNAME [IDS] [-l error|warn|info|debug] [-e true|false] [-S] [-d] [-i <value>] [-F]
684
+ [--suffix <value>]
677
685
 
678
686
  ARGUMENTS
679
687
  ENTITYNAME The name of the entity type to update
@@ -688,9 +696,11 @@ FLAGS
688
696
  -i, --inputFile=<value> File containing properties to update the entity (optional)
689
697
  -l, --logLevel=<option> The Log Level
690
698
  <options: error|warn|info|debug>
699
+ --suffix=<value> Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets
700
+ /<entities>/synchronous)
691
701
 
692
702
  DESCRIPTION
693
- (BETA) Update an entity
703
+ Update an entity
694
704
 
695
705
  EXAMPLES
696
706
  $ contrail entities update color 123 -- name=obsidian hex=000000
@@ -14,6 +14,7 @@ export default class CreateEntity extends BaseCommand {
14
14
  generateValues: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
15
  readableIds: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
16
  skipHydrateRelationships: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
17
+ suffix: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
17
18
  alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
18
19
  logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
19
20
  };
@@ -145,7 +145,7 @@ const FIND_VALID_VALUE_MAP = {
145
145
  ],
146
146
  };
147
147
  class CreateEntity extends basecommand_1.BaseCommand {
148
- static description = '(BETA) Create entities';
148
+ static description = 'Create entities';
149
149
  static examples = [
150
150
  'contrail entities create color -g -n=10 # creates 10 random colors',
151
151
  'contrail entities create color -- name="Electric Blue" green=1 red=2 blue=3 # creates one color with specified properties only',
@@ -200,6 +200,10 @@ class CreateEntity extends basecommand_1.BaseCommand {
200
200
  required: false,
201
201
  default: false,
202
202
  }),
203
+ suffix: core_1.Flags.string({
204
+ description: 'Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)',
205
+ required: false,
206
+ }),
203
207
  };
204
208
  static args = {
205
209
  path: core_1.Args.string({
@@ -211,10 +215,9 @@ class CreateEntity extends basecommand_1.BaseCommand {
211
215
  static strict = false;
212
216
  async run() {
213
217
  (0, common_1.routeConsoleLogToStderr)();
214
- console.error('🚨 This command is in BETA and may change or be removed at any time.');
215
218
  await (0, login_1.authUser)();
216
219
  const { flags, argv, args } = await this.parse(CreateEntity);
217
- const { count, dryRun: isDryRun, inputFile: inputFilename, outputFile: outputFilename, generateValues: shouldGenerateMissingValues, readableIds: shouldUseReadableIds, skipHydrateRelationships, } = flags;
220
+ const { count, dryRun: isDryRun, inputFile: inputFilename, outputFile: outputFilename, generateValues: shouldGenerateMissingValues, readableIds: shouldUseReadableIds, skipHydrateRelationships, suffix, } = flags;
218
221
  const { path: entityTypePath } = args;
219
222
  // Global flags is a little hacky, but it was easier
220
223
  // and it doesn't see *that* bad because they are only valid
@@ -260,7 +263,7 @@ class CreateEntity extends basecommand_1.BaseCommand {
260
263
  }
261
264
  try {
262
265
  const entitiesToCreate = await buildEntitiesToCreate(entityTypePath, partialEntities, propertySpecBySlug);
263
- const summary = await createEntities(entityName, entitiesToCreate);
266
+ const summary = await createEntities(entityName, entitiesToCreate, suffix);
264
267
  if (outputFilename) {
265
268
  fs_1.default.writeFileSync(outputFilename, JSON.stringify(summary, null, 2));
266
269
  console.error(`Output saved to ${outputFilename}`);
@@ -295,13 +298,13 @@ function summarizeSettledCreates(inputs, settled) {
295
298
  return { created, failed };
296
299
  }
297
300
  exports.summarizeSettledCreates = summarizeSettledCreates;
298
- async function createEntities(entityName, entities) {
301
+ async function createEntities(entityName, entities, suffix) {
299
302
  if (IS_DRY_RUN) {
300
303
  console.error(`Dry run mode: not creating entities, but simulating creation for ${entityName}`);
301
304
  return { created: entities, failed: [] };
302
305
  }
303
306
  const limit = (0, p_limit_1.default)(20);
304
- const settled = await Promise.allSettled(entities.map(async (e) => limit(() => createEntity(entityName, e))));
307
+ const settled = await Promise.allSettled(entities.map(async (e) => limit(() => createEntity(entityName, e, suffix))));
305
308
  return summarizeSettledCreates(entities, settled);
306
309
  }
307
310
  async function buildEntitiesToCreate(entityTypePath, entities, propertySpecificationBySlug) {
@@ -541,12 +544,17 @@ function castValueToPropertyType(value, propertyType) {
541
544
  }
542
545
  }
543
546
  exports.castValueToPropertyType = castValueToPropertyType;
544
- async function createEntity(entityName, entity) {
547
+ async function createEntity(entityName, entity, suffix) {
545
548
  const client = new sdk_1.Entities();
546
549
  const skipHydrateRelationships = SHOULD_SKIP_HYDRATE_RELATIONSHIPS;
547
- if (entityName === 'assortment-item')
550
+ if (entityName === 'assortment-item' && !suffix)
548
551
  return (0, entities_1.createAssortmentItem)(entity, { skipHydrateRelationships }); // special case
549
- const response = await client.create({ entityName, object: entity, skipHydrateRelationships });
552
+ const response = await client.create({
553
+ entityName,
554
+ object: entity,
555
+ skipHydrateRelationships,
556
+ suffix,
557
+ });
550
558
  return response;
551
559
  }
552
560
  function getReferencedObjectPaths(type) {
@@ -527,7 +527,7 @@ describe('castValueToPropertyType array coercion', () => {
527
527
  ]);
528
528
  });
529
529
  it('should keep a comma value literal on a multi-value property and warn with the repeated-key form', () => {
530
- const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { });
530
+ const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
531
531
  expect((0, create_1.castValueToPropertyType)('option,family', types_1.PropertyType.MultiSelect)).toEqual([
532
532
  'option,family',
533
533
  ]);
@@ -535,7 +535,7 @@ describe('castValueToPropertyType array coercion', () => {
535
535
  consoleError.mockRestore();
536
536
  });
537
537
  it('should keep commas literal on string properties without warning', () => {
538
- const consoleError = jest.spyOn(console, 'error').mockImplementation(() => { });
538
+ const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
539
539
  expect((0, create_1.castValueToPropertyType)('my,name', types_1.PropertyType.String)).toBe('my,name');
540
540
  expect(consoleError).not.toHaveBeenCalled();
541
541
  consoleError.mockRestore();
@@ -5,6 +5,7 @@ export default class DeleteEntity extends BaseCommand {
5
5
  static flags: {
6
6
  dryRun: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
7
  inputFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ suffix: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
9
  alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
10
  logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
11
  };
@@ -10,7 +10,7 @@ const common_1 = require("../../common");
10
10
  const file_helpers_1 = require("../../helpers/file-helpers");
11
11
  let IS_DRY_RUN = false;
12
12
  class DeleteEntity extends basecommand_1.BaseCommand {
13
- static description = '(BETA) Delete entities by id';
13
+ static description = 'Delete entities by id';
14
14
  static examples = [
15
15
  'contrail entities delete color 123',
16
16
  'contrail entities delete color id1 id2 id3',
@@ -26,10 +26,14 @@ class DeleteEntity extends basecommand_1.BaseCommand {
26
26
  }),
27
27
  inputFile: core_1.Flags.string({
28
28
  char: 'i',
29
- description: 'A file with a JSON array of objects; only each object\'s id is used. Deletes every entity in the file.',
29
+ description: "A file with a JSON array of objects; only each object's id is used. Deletes every entity in the file.",
30
30
  required: false,
31
31
  multiple: false,
32
32
  }),
33
+ suffix: core_1.Flags.string({
34
+ description: 'Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)',
35
+ required: false,
36
+ }),
33
37
  };
34
38
  static args = {
35
39
  entityName: core_1.Args.string({
@@ -44,11 +48,10 @@ class DeleteEntity extends basecommand_1.BaseCommand {
44
48
  static strict = false;
45
49
  async run() {
46
50
  (0, common_1.routeConsoleLogToStderr)();
47
- console.error('🚨 This command is in BETA and may change or be removed at any time.');
48
51
  await (0, login_1.authUser)();
49
52
  const { args, flags, argv } = await this.parse(DeleteEntity);
50
53
  const { entityName } = args;
51
- const { inputFile: inputFilename } = flags;
54
+ const { inputFile: inputFilename, suffix } = flags;
52
55
  if (flags.dryRun) {
53
56
  IS_DRY_RUN = true;
54
57
  console.error('Running in dry run mode. No entities will be deleted.');
@@ -72,7 +75,7 @@ class DeleteEntity extends basecommand_1.BaseCommand {
72
75
  }
73
76
  const ids = rowsWithId ? rowsWithId.map((item) => item.id) : positionalIds;
74
77
  try {
75
- const response = await deleteEntityByIds(entityName, ids);
78
+ const response = await deleteEntityByIds(entityName, ids, suffix);
76
79
  if (IS_DRY_RUN) {
77
80
  console.error('simulated deletion of entities:');
78
81
  }
@@ -91,12 +94,12 @@ class DeleteEntity extends basecommand_1.BaseCommand {
91
94
  }
92
95
  }
93
96
  exports.default = DeleteEntity;
94
- async function deleteEntityByIds(entityName, ids) {
97
+ async function deleteEntityByIds(entityName, ids, suffix) {
95
98
  const idsDeleted = new Set();
96
99
  const idsNotFound = new Set();
97
100
  const idsErrored = new Set();
98
101
  const limit = (0, p_limit_1.default)(10);
99
- const deletePromises = ids.map((id) => limit(() => deleteEntityById(entityName, id).then((entity) => {
102
+ const deletePromises = ids.map((id) => limit(() => deleteEntityById(entityName, id, suffix).then((entity) => {
100
103
  if (!entity) {
101
104
  idsNotFound.add(id);
102
105
  return null;
@@ -119,12 +122,12 @@ async function deleteEntityByIds(entityName, ids) {
119
122
  idsErrored: Array.from(idsErrored),
120
123
  };
121
124
  }
122
- async function deleteEntityById(entityName, id) {
125
+ async function deleteEntityById(entityName, id, suffix) {
123
126
  const client = new sdk_1.Entities();
124
127
  if (IS_DRY_RUN) {
125
128
  return client.get({ entityName, id, ignore404: true }).catch(throwIfNot404);
126
129
  }
127
- return client.delete({ entityName, id, ignore404: true }).catch(throwIfNot404);
130
+ return client.delete({ entityName, id, ignore404: true, suffix }).catch(throwIfNot404);
128
131
  }
129
132
  function throwIfNot404(e) {
130
133
  const { statusCode } = parseSDKErrorMessage(e);
@@ -10,6 +10,7 @@ export default class GetEntity extends BaseCommand {
10
10
  skipHydrateRelationships: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
11
  take: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
12
12
  relations: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
13
+ suffix: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
13
14
  alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
14
15
  logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
15
16
  };
@@ -13,7 +13,7 @@ const batchFetch_1 = require("../../helpers/entities/batchFetch");
13
13
  let SHOULD_USE_PAGINATED_FIND = false;
14
14
  let MAX_NUMBER_OF_ENTITIES_TO_FETCH = undefined;
15
15
  class GetEntity extends basecommand_1.BaseCommand {
16
- static description = '(BETA) Get an entity';
16
+ static description = 'Get an entity';
17
17
  static examples = [
18
18
  'contrail entities get color 123',
19
19
  'contrail entities get color id1 id2 id3',
@@ -62,6 +62,10 @@ class GetEntity extends basecommand_1.BaseCommand {
62
62
  description: 'Comma-separated list of relations to include in the output',
63
63
  required: false,
64
64
  }),
65
+ suffix: core_1.Flags.string({
66
+ description: 'Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)',
67
+ required: false,
68
+ }),
65
69
  };
66
70
  static args = {
67
71
  typePath: core_1.Args.string({
@@ -76,11 +80,10 @@ class GetEntity extends basecommand_1.BaseCommand {
76
80
  static strict = false;
77
81
  async run() {
78
82
  (0, common_1.routeConsoleLogToStderr)();
79
- console.error('🚨 This command is in BETA and may change or be removed at any time.');
80
83
  await (0, login_1.authUser)();
81
84
  const { args, flags, argv } = await this.parse(GetEntity);
82
85
  const { typePath } = args;
83
- const { outputFile, paginated: shouldUsePaginatedFind, propertySlugs, relations: relationString, skip, skipHydrateRelationships, take, } = flags;
86
+ const { outputFile, paginated: shouldUsePaginatedFind, propertySlugs, relations: relationString, skip, skipHydrateRelationships, suffix, take, } = flags;
84
87
  SHOULD_USE_PAGINATED_FIND = shouldUsePaginatedFind;
85
88
  MAX_NUMBER_OF_ENTITIES_TO_FETCH = take;
86
89
  if (MAX_NUMBER_OF_ENTITIES_TO_FETCH)
@@ -121,6 +124,7 @@ class GetEntity extends basecommand_1.BaseCommand {
121
124
  criteria: convertPropertySpecIntoCriteria(propertySpecBySlug),
122
125
  skipHydrateRelationships,
123
126
  skip,
127
+ suffix,
124
128
  relations,
125
129
  propertySlugs: propertySlugsArray,
126
130
  });
@@ -143,6 +147,7 @@ class GetEntity extends basecommand_1.BaseCommand {
143
147
  relations,
144
148
  propertySlugs: propertySlugsArray,
145
149
  skip,
150
+ suffix,
146
151
  take,
147
152
  });
148
153
  if (outputFile) {
@@ -161,7 +166,7 @@ class GetEntity extends basecommand_1.BaseCommand {
161
166
  }
162
167
  exports.default = GetEntity;
163
168
  async function getEntityByTypePath(typePath, propertySpec, options = {}) {
164
- const { propertySlugs, relations, skipHydrateRelationships, skip, take } = options;
169
+ const { propertySlugs, relations, skipHydrateRelationships, skip, suffix, take } = options;
165
170
  const id = propertySpec?.get('id')?.toString() || undefined;
166
171
  const criteria = convertPropertySpecIntoCriteria(propertySpec || new Map());
167
172
  const { root: entityName } = (0, types_1.parseTypePath)(typePath);
@@ -175,6 +180,7 @@ async function getEntityByTypePath(typePath, propertySpec, options = {}) {
175
180
  propertySlugs,
176
181
  criteria,
177
182
  skip,
183
+ suffix,
178
184
  take,
179
185
  });
180
186
  }
@@ -187,19 +193,21 @@ function convertPropertySpecIntoCriteria(propertySpec) {
187
193
  }
188
194
  async function find(entityName, options = {}) {
189
195
  const client = new sdk_1.Entities();
190
- const { criteria, id, propertySlugs, skipHydrateRelationships, relations, skip, take } = options;
196
+ const { criteria, id, propertySlugs, skipHydrateRelationships, relations, skip, suffix, take } = options;
191
197
  const queryOptions = {
192
198
  entityName,
193
199
  skipHydrateRelationships,
194
200
  relations,
195
201
  skip,
202
+ suffix,
196
203
  take,
197
204
  };
198
205
  if (propertySlugs && propertySlugs.length > 0) {
199
206
  queryOptions.customParams = { propertySlugs: propertySlugs.join(',') };
200
207
  }
201
208
  const payload = id ? { id, ...queryOptions } : { criteria, ...queryOptions };
202
- if ((0, batchFetch_1.canBatchFetchEntity)(entityName)) {
209
+ // a suffix names a specific sub-route, so it always goes through the plain client
210
+ if ((0, batchFetch_1.canBatchFetchEntity)(entityName) && !suffix) {
203
211
  return (0, batchFetch_1.batchFetch)(entityName, payload);
204
212
  }
205
213
  else {
@@ -35,35 +35,41 @@ describe('entities command grammar: entities <verb> <typePath> [ID...]', () => {
35
35
  });
36
36
  });
37
37
  describe('positional id arguments', () => {
38
- it('get accepts variadic positional ids', () => {
38
+ it('should accept variadic positional ids on get', () => {
39
39
  expect(get_1.default.strict).toBe(false);
40
40
  expect(get_1.default.args.ids).toBeDefined();
41
41
  });
42
- it('update accepts variadic positional ids and has no single id arg', () => {
42
+ it('should accept variadic positional ids on update with no single id arg', () => {
43
43
  expect(update_1.default.strict).toBe(false);
44
44
  expect(update_1.default.args.ids).toBeDefined();
45
45
  expect(update_1.default.args.id).toBeUndefined();
46
46
  });
47
- it('delete accepts variadic positional ids and has no --id flag', () => {
47
+ it('should accept variadic positional ids on delete with no --id flag', () => {
48
48
  expect(delete_1.default.strict).toBe(false);
49
49
  expect(delete_1.default.args.ids).toBeDefined();
50
50
  expect(delete_1.default.flags.id).toBeUndefined();
51
51
  });
52
52
  });
53
53
  describe('flag alignment across verbs', () => {
54
- it('the input file is -i, --inputFile on every verb that accepts one', () => {
54
+ it('should name the input file -i, --inputFile on every verb that accepts one', () => {
55
55
  expect(create_1.default.flags.inputFile.char).toBe('i');
56
56
  expect(update_1.default.flags.inputFile.char).toBe('i');
57
57
  expect(delete_1.default.flags.inputFile.char).toBe('i');
58
58
  expect(delete_1.default.flags.input).toBeUndefined();
59
59
  });
60
- it('skipHydrateRelationships is -S on every verb', () => {
60
+ it('should map skipHydrateRelationships to -S on every verb', () => {
61
61
  expect(create_1.default.flags.skipHydrateRelationships.char).toBe('S');
62
62
  expect(get_1.default.flags.skipHydrateRelationships.char).toBe('S');
63
63
  expect(update_1.default.flags.skipHydrateRelationships.char).toBe('S');
64
64
  });
65
- it('get keeps -s for --skip', () => {
65
+ it('should keep -s for --skip on get', () => {
66
66
  expect(get_1.default.flags.skip.char).toBe('s');
67
67
  });
68
+ it('should expose --suffix on every verb to reach entity sub-routes', () => {
69
+ expect(create_1.default.flags.suffix).toBeDefined();
70
+ expect(get_1.default.flags.suffix).toBeDefined();
71
+ expect(update_1.default.flags.suffix).toBeDefined();
72
+ expect(delete_1.default.flags.suffix).toBeDefined();
73
+ });
68
74
  });
69
75
  });
@@ -7,6 +7,7 @@ export default class UpdateEntity extends BaseCommand {
7
7
  dryRun: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
8
  inputFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
9
  force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ suffix: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
11
  alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
11
12
  logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
12
13
  };
@@ -11,7 +11,7 @@ const common_1 = require("../../common");
11
11
  const propertySpecifications_1 = require("../../helpers/propertySpecifications/propertySpecifications");
12
12
  const file_helpers_1 = require("../../helpers/file-helpers");
13
13
  class UpdateEntity extends basecommand_1.BaseCommand {
14
- static description = '(BETA) Update an entity';
14
+ static description = 'Update an entity';
15
15
  static examples = [
16
16
  'contrail entities update color 123 -- name=obsidian hex=000000',
17
17
  'contrail entities update item id1 id2 id3 -- status=archived # apply the same change to several entities',
@@ -41,6 +41,10 @@ class UpdateEntity extends basecommand_1.BaseCommand {
41
41
  description: 'Force update without validation. Internal use only',
42
42
  required: false,
43
43
  }),
44
+ suffix: core_1.Flags.string({
45
+ description: 'Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)',
46
+ required: false,
47
+ }),
44
48
  };
45
49
  static args = {
46
50
  entityName: core_1.Args.string({
@@ -55,11 +59,10 @@ class UpdateEntity extends basecommand_1.BaseCommand {
55
59
  static strict = false;
56
60
  async run() {
57
61
  (0, common_1.routeConsoleLogToStderr)();
58
- console.error('🚨 This command is in BETA and may change or be removed at any time.');
59
62
  await (0, login_1.authUser)();
60
63
  const { args, argv } = await this.parse(UpdateEntity);
61
64
  const { entityName } = args;
62
- const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, force, } = this.flags;
65
+ const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, suffix, force, } = this.flags;
63
66
  const { ids: positionalIds, specs } = (0, propertySpecifications_1.partitionIdsAndSpecs)(argv.slice(1));
64
67
  const propertySpec = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
65
68
  if (propertySpec.has('id') || propertySpec.has('ids')) {
@@ -89,7 +92,11 @@ class UpdateEntity extends basecommand_1.BaseCommand {
89
92
  const id = entity.id;
90
93
  return isDryRun
91
94
  ? mockUpdateById(entityName, id, newEntityState, { skipHydrateRelationships })
92
- : updateEntityById(entityName, id, newEntityState, { skipHydrateRelationships, force });
95
+ : updateEntityById(entityName, id, newEntityState, {
96
+ skipHydrateRelationships,
97
+ suffix,
98
+ force,
99
+ });
93
100
  }
94
101
  try {
95
102
  const limit = (0, p_limit_1.default)(20);
@@ -131,8 +138,8 @@ function summarizeSettledUpdates(inputs, settled, skipped) {
131
138
  exports.summarizeSettledUpdates = summarizeSettledUpdates;
132
139
  async function updateEntityById(entityName, id, changes, options = {}) {
133
140
  const client = new sdk_1.Entities();
134
- const { skipHydrateRelationships, force } = options;
135
- return client.update({ entityName, id, object: changes, skipHydrateRelationships });
141
+ const { skipHydrateRelationships, suffix, force } = options;
142
+ return client.update({ entityName, id, object: changes, skipHydrateRelationships, suffix });
136
143
  }
137
144
  async function mockUpdateById(entityName, id, changes, options = {}) {
138
145
  const client = new sdk_1.Entities();
@@ -31,8 +31,7 @@ export declare function parsePropertySpecification(spec: PropertySpecificationSt
31
31
  export declare function parsePropertySpecificationsIntoMap(propertySpecifications: PropertySpecificationString[]): PropertySpecificationBySlug;
32
32
  /**
33
33
  * Splits command argv tokens (after the typePath) into positional ids and
34
- * property specifications. A token containing '=' is a property specification
35
- * candidate; every other token is an id.
34
+ * property specifications.
36
35
  */
37
36
  export declare function partitionIdsAndSpecs(tokens: string[]): {
38
37
  ids: string[];
@@ -71,8 +71,6 @@ function parsePropertySpecificationsIntoMap(propertySpecifications) {
71
71
  propertySpecBySlug.set(slug, idsArray);
72
72
  continue;
73
73
  }
74
- // Repeating a key accumulates values into an array; commas in every other
75
- // value are literal.
76
74
  const existing = propertySpecBySlug.get(slug);
77
75
  if (propertySpecBySlug.has(slug) && existing !== undefined && value !== undefined) {
78
76
  propertySpecBySlug.set(slug, Array.isArray(existing) ? [...existing, value] : [existing, value]);
@@ -85,8 +83,7 @@ function parsePropertySpecificationsIntoMap(propertySpecifications) {
85
83
  exports.parsePropertySpecificationsIntoMap = parsePropertySpecificationsIntoMap;
86
84
  /**
87
85
  * Splits command argv tokens (after the typePath) into positional ids and
88
- * property specifications. A token containing '=' is a property specification
89
- * candidate; every other token is an id.
86
+ * property specifications.
90
87
  */
91
88
  function partitionIdsAndSpecs(tokens) {
92
89
  return {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const logger_1 = tslib_1.__importDefault(require("./logger"));
5
5
  describe('logger', () => {
6
- it('writes every level to stderr so stdout carries only command output', () => {
6
+ it('should write every log level to stderr so stdout carries only command output', () => {
7
7
  const consoleTransport = logger_1.default.transports[0];
8
8
  for (const level of ['error', 'warn', 'info', 'debug']) {
9
9
  expect(consoleTransport.stderrLevels[level]).toBe(true);
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.6.0-alpha-8-alpha.e4ae6bd",
2
+ "version": "3.6.0-alpha-8-alpha.7d24daa",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
@@ -675,7 +675,7 @@
675
675
  },
676
676
  "entities:create": {
677
677
  "id": "entities:create",
678
- "description": "(BETA) Create entities",
678
+ "description": "Create entities",
679
679
  "strict": false,
680
680
  "pluginName": "@contrail/cli",
681
681
  "pluginAlias": "@contrail/cli",
@@ -775,6 +775,13 @@
775
775
  "description": "If true, does not hydrate relationships for the created entities",
776
776
  "required": false,
777
777
  "allowNo": false
778
+ },
779
+ "suffix": {
780
+ "name": "suffix",
781
+ "type": "option",
782
+ "description": "Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)",
783
+ "required": false,
784
+ "multiple": false
778
785
  }
779
786
  },
780
787
  "args": {
@@ -787,7 +794,7 @@
787
794
  },
788
795
  "entities:delete": {
789
796
  "id": "entities:delete",
790
- "description": "(BETA) Delete entities by id",
797
+ "description": "Delete entities by id",
791
798
  "strict": false,
792
799
  "pluginName": "@contrail/cli",
793
800
  "pluginAlias": "@contrail/cli",
@@ -841,6 +848,13 @@
841
848
  "description": "A file with a JSON array of objects; only each object's id is used. Deletes every entity in the file.",
842
849
  "required": false,
843
850
  "multiple": false
851
+ },
852
+ "suffix": {
853
+ "name": "suffix",
854
+ "type": "option",
855
+ "description": "Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)",
856
+ "required": false,
857
+ "multiple": false
844
858
  }
845
859
  },
846
860
  "args": {
@@ -858,7 +872,7 @@
858
872
  },
859
873
  "entities:get": {
860
874
  "id": "entities:get",
861
- "description": "(BETA) Get an entity",
875
+ "description": "Get an entity",
862
876
  "strict": false,
863
877
  "pluginName": "@contrail/cli",
864
878
  "pluginAlias": "@contrail/cli",
@@ -955,6 +969,13 @@
955
969
  "description": "Comma-separated list of relations to include in the output",
956
970
  "required": false,
957
971
  "multiple": false
972
+ },
973
+ "suffix": {
974
+ "name": "suffix",
975
+ "type": "option",
976
+ "description": "Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)",
977
+ "required": false,
978
+ "multiple": false
958
979
  }
959
980
  },
960
981
  "args": {
@@ -972,7 +993,7 @@
972
993
  },
973
994
  "entities:update": {
974
995
  "id": "entities:update",
975
- "description": "(BETA) Update an entity",
996
+ "description": "Update an entity",
976
997
  "strict": false,
977
998
  "pluginName": "@contrail/cli",
978
999
  "pluginAlias": "@contrail/cli",
@@ -1042,6 +1063,13 @@
1042
1063
  "description": "Force update without validation. Internal use only",
1043
1064
  "required": false,
1044
1065
  "allowNo": false
1066
+ },
1067
+ "suffix": {
1068
+ "name": "suffix",
1069
+ "type": "option",
1070
+ "description": "Append a sub-route to the entity endpoint (e.g. --suffix=synchronous targets /<entities>/synchronous)",
1071
+ "required": false,
1072
+ "multiple": false
1045
1073
  }
1046
1074
  },
1047
1075
  "args": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "3.6.0-alpha-8-alpha.e4ae6bd",
3
+ "version": "3.6.0-alpha-8-alpha.7d24daa",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",