@contrail/cli 3.6.0-alpha-8-alpha.d292a3d → 3.6.0-alpha-8-alpha.828359d
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 +4 -5
- package/dist/commands/entities/create.js +1 -5
- package/dist/commands/entities/create.spec.js +0 -2
- package/dist/commands/entities/delete.js +11 -17
- package/dist/commands/entities/get.js +10 -1
- package/dist/commands/entities/update.d.ts +0 -1
- package/dist/commands/entities/update.js +4 -12
- package/dist/helpers/propertySpecifications/propertySpecifications.js +2 -1
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +5 -0
- package/oclif.manifest.json +3 -11
- package/package.json +1 -1
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.
|
|
20
|
+
@contrail/cli/3.6.0-alpha-8-alpha.828359d linux-x64 node-v24.21.0
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
|
@@ -686,7 +686,7 @@ Update an entity
|
|
|
686
686
|
|
|
687
687
|
```
|
|
688
688
|
USAGE
|
|
689
|
-
$ contrail entities update ENTITYNAME [IDS] [-l error|warn|info|debug] [-e true|false] [-S] [-d] [-i <value>]
|
|
689
|
+
$ contrail entities update ENTITYNAME [IDS] [-l error|warn|info|debug] [-e true|false] [-S] [-d] [-i <value>]
|
|
690
690
|
[--suffix <value>] [--apiVersion 1|2]
|
|
691
691
|
|
|
692
692
|
ARGUMENTS
|
|
@@ -694,12 +694,11 @@ ARGUMENTS
|
|
|
694
694
|
IDS One or more entity ids to update
|
|
695
695
|
|
|
696
696
|
FLAGS
|
|
697
|
-
-F, --force Force update without validation. Internal use only
|
|
698
697
|
-S, --skipHydrateRelationships Skip hydrating relationships when updating the entity
|
|
699
|
-
-d, --dryRun If true, does not
|
|
698
|
+
-d, --dryRun If true, does not update the entities but simulates the update
|
|
700
699
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
701
700
|
<options: true|false>
|
|
702
|
-
-i, --inputFile=<value>
|
|
701
|
+
-i, --inputFile=<value> A file with a JSON array of {id, ...} objects; each object is one entity to update.
|
|
703
702
|
-l, --logLevel=<option> The Log Level
|
|
704
703
|
<options: error|warn|info|debug>
|
|
705
704
|
--apiVersion=<option> Api-Version header to send; selects the V1 or V2 variant of the endpoint
|
|
@@ -290,8 +290,6 @@ class CreateEntity extends basecommand_1.BaseCommand {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
exports.default = CreateEntity;
|
|
293
|
-
// Every row is attempted; a failed row never stops the run. The summary pairs
|
|
294
|
-
// each failed input with its error so the failed rows can be retried as-is.
|
|
295
293
|
function summarizeSettledCreates(inputs, settled) {
|
|
296
294
|
const created = [];
|
|
297
295
|
const failed = [];
|
|
@@ -538,9 +536,7 @@ function castValueToPropertyType(value, propertyType) {
|
|
|
538
536
|
if (Array.isArray(value)) {
|
|
539
537
|
return value;
|
|
540
538
|
}
|
|
541
|
-
//
|
|
542
|
-
// comma in it. Warn with the repeated-key equivalent instead of
|
|
543
|
-
// guessing at a split.
|
|
539
|
+
// the old comma syntax parses as one literal value; warn instead of guessing at a split
|
|
544
540
|
if (typeof value === 'string' && value.includes(',')) {
|
|
545
541
|
console.error(`Value "${value}" for a multi-value property contains a comma and is treated as one literal value. To pass several values, repeat the key: slug=${value
|
|
546
542
|
.split(',')
|
|
@@ -333,8 +333,6 @@ describe('Entity Creation E2E Happy Path', () => {
|
|
|
333
333
|
const propertySpecs = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)([
|
|
334
334
|
'roles=option',
|
|
335
335
|
]);
|
|
336
|
-
// a single roles value stays scalar at the parse layer and is
|
|
337
|
-
// normalized to an array where it is consumed
|
|
338
336
|
const roles = [].concat(propertySpecs.get('roles'));
|
|
339
337
|
// Test that property level matching works with specified roles
|
|
340
338
|
expect((0, create_1.doesPropertyLevelMatchRole)(types_1.PropertyLevel.OPTION, roles[0])).toBe(true);
|
|
@@ -91,8 +91,8 @@ class DeleteEntity extends basecommand_1.BaseCommand {
|
|
|
91
91
|
console.error('deleted entities:');
|
|
92
92
|
}
|
|
93
93
|
this.log(JSON.stringify(response, null, 2));
|
|
94
|
-
if (response.
|
|
95
|
-
console.error(`${response.
|
|
94
|
+
if (response.failed.length > 0) {
|
|
95
|
+
console.error(`${response.failed.length} of ${ids.length} deletes failed.`);
|
|
96
96
|
process.exitCode = 1;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -103,32 +103,26 @@ class DeleteEntity extends basecommand_1.BaseCommand {
|
|
|
103
103
|
}
|
|
104
104
|
exports.default = DeleteEntity;
|
|
105
105
|
async function deleteEntityByIds(entityName, ids, routing = {}) {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
106
|
+
const deletedIds = [];
|
|
107
|
+
const notFoundIds = [];
|
|
108
|
+
const failed = [];
|
|
109
109
|
const limit = (0, p_limit_1.default)(10);
|
|
110
110
|
const deletePromises = ids.map((id) => limit(() => deleteEntityById(entityName, id, routing).then((entity) => {
|
|
111
111
|
if (!entity) {
|
|
112
|
-
|
|
113
|
-
return
|
|
112
|
+
notFoundIds.push(id);
|
|
113
|
+
return;
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
return entity;
|
|
115
|
+
deletedIds.push(id);
|
|
117
116
|
}, (error) => {
|
|
118
117
|
if (error.message.includes('not found')) {
|
|
119
|
-
|
|
118
|
+
notFoundIds.push(id);
|
|
120
119
|
}
|
|
121
120
|
else {
|
|
122
|
-
|
|
121
|
+
failed.push({ id, error: (0, common_1.sdkErrorMessage)(error) });
|
|
123
122
|
}
|
|
124
|
-
return null;
|
|
125
123
|
})));
|
|
126
124
|
await Promise.all(deletePromises);
|
|
127
|
-
return {
|
|
128
|
-
idsDeleted: Array.from(idsDeleted),
|
|
129
|
-
idsNotFound: Array.from(idsNotFound),
|
|
130
|
-
idsErrored: Array.from(idsErrored),
|
|
131
|
-
};
|
|
125
|
+
return { deletedIds, notFoundIds, failed };
|
|
132
126
|
}
|
|
133
127
|
async function deleteEntityById(entityName, id, routing = {}) {
|
|
134
128
|
const client = new sdk_1.Entities();
|
|
@@ -157,6 +157,9 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
157
157
|
apiVersion: apiVersion,
|
|
158
158
|
take,
|
|
159
159
|
});
|
|
160
|
+
if (response === null) {
|
|
161
|
+
this.error(`Entity not found: ${propertySpecBySlug.get('id')}`, { exit: 1 });
|
|
162
|
+
}
|
|
160
163
|
if (outputFile) {
|
|
161
164
|
fs_1.default.writeFileSync(outputFile, JSON.stringify(response, null, 2));
|
|
162
165
|
console.error(`Output saved to ${outputFile}`);
|
|
@@ -167,6 +170,9 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
catch (e) {
|
|
173
|
+
if (e.message?.startsWith('Entity not found:')) {
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
170
176
|
this.error(`Error fetching entity: ${e.message}`);
|
|
171
177
|
}
|
|
172
178
|
}
|
|
@@ -182,6 +188,8 @@ async function getEntityByTypePath(typePath, propertySpec, options = {}) {
|
|
|
182
188
|
}
|
|
183
189
|
return find(entityName, {
|
|
184
190
|
id,
|
|
191
|
+
// a missing id returns null (no retries); the command turns it into a clean not-found error
|
|
192
|
+
ignore404: id !== undefined,
|
|
185
193
|
skipHydrateRelationships,
|
|
186
194
|
relations,
|
|
187
195
|
propertySlugs,
|
|
@@ -201,9 +209,10 @@ function convertPropertySpecIntoCriteria(propertySpec) {
|
|
|
201
209
|
}
|
|
202
210
|
async function find(entityName, options = {}) {
|
|
203
211
|
const client = new sdk_1.Entities();
|
|
204
|
-
const { criteria, id, propertySlugs, skipHydrateRelationships, relations, skip, suffix, apiVersion, take, } = options;
|
|
212
|
+
const { criteria, id, ignore404, propertySlugs, skipHydrateRelationships, relations, skip, suffix, apiVersion, take, } = options;
|
|
205
213
|
const queryOptions = {
|
|
206
214
|
entityName,
|
|
215
|
+
ignore404,
|
|
207
216
|
skipHydrateRelationships,
|
|
208
217
|
relations,
|
|
209
218
|
skip,
|
|
@@ -6,7 +6,6 @@ export default class UpdateEntity extends BaseCommand {
|
|
|
6
6
|
skipHydrateRelationships: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
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
|
-
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
9
|
suffix: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
10
|
apiVersion: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
11
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
@@ -27,18 +27,13 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
27
27
|
}),
|
|
28
28
|
dryRun: core_1.Flags.boolean({
|
|
29
29
|
char: 'd',
|
|
30
|
-
description: 'If true, does not
|
|
30
|
+
description: 'If true, does not update the entities but simulates the update',
|
|
31
31
|
required: false,
|
|
32
32
|
default: false,
|
|
33
33
|
}),
|
|
34
34
|
inputFile: core_1.Flags.string({
|
|
35
35
|
char: 'i',
|
|
36
|
-
description: '
|
|
37
|
-
required: false,
|
|
38
|
-
}),
|
|
39
|
-
force: core_1.Flags.boolean({
|
|
40
|
-
char: 'F',
|
|
41
|
-
description: 'Force update without validation. Internal use only',
|
|
36
|
+
description: 'A file with a JSON array of {id, ...} objects; each object is one entity to update.',
|
|
42
37
|
required: false,
|
|
43
38
|
}),
|
|
44
39
|
suffix: core_1.Flags.string({
|
|
@@ -67,7 +62,7 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
67
62
|
await (0, login_1.authUser)();
|
|
68
63
|
const { args, argv } = await this.parse(UpdateEntity);
|
|
69
64
|
const { entityName } = args;
|
|
70
|
-
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, suffix, apiVersion,
|
|
65
|
+
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, suffix, apiVersion, } = this.flags;
|
|
71
66
|
const { ids: positionalIds, specs } = (0, propertySpecifications_1.partitionIdsAndSpecs)(argv.slice(1));
|
|
72
67
|
const propertySpec = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
|
|
73
68
|
if (propertySpec.has('id') || propertySpec.has('ids')) {
|
|
@@ -101,7 +96,6 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
101
96
|
skipHydrateRelationships,
|
|
102
97
|
suffix,
|
|
103
98
|
apiVersion: apiVersion,
|
|
104
|
-
force,
|
|
105
99
|
});
|
|
106
100
|
}
|
|
107
101
|
try {
|
|
@@ -123,8 +117,6 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
exports.default = UpdateEntity;
|
|
126
|
-
// Every row is attempted; a failed row never stops the run. The summary pairs
|
|
127
|
-
// each failed id with its error so the failed rows can be retried.
|
|
128
120
|
function summarizeSettledUpdates(inputs, settled, skipped) {
|
|
129
121
|
const updated = [];
|
|
130
122
|
const failed = [];
|
|
@@ -144,7 +136,7 @@ function summarizeSettledUpdates(inputs, settled, skipped) {
|
|
|
144
136
|
exports.summarizeSettledUpdates = summarizeSettledUpdates;
|
|
145
137
|
async function updateEntityById(entityName, id, changes, options = {}) {
|
|
146
138
|
const client = new sdk_1.Entities();
|
|
147
|
-
const { skipHydrateRelationships, suffix, apiVersion
|
|
139
|
+
const { skipHydrateRelationships, suffix, apiVersion } = options;
|
|
148
140
|
return client.update({
|
|
149
141
|
entityName,
|
|
150
142
|
id,
|
|
@@ -68,7 +68,8 @@ function parsePropertySpecificationsIntoMap(propertySpecifications) {
|
|
|
68
68
|
// ids is the one comma-split exception: it is a list of ids by definition
|
|
69
69
|
// and ids never contain commas.
|
|
70
70
|
const idsArray = value.split(',').map((id) => id.trim());
|
|
71
|
-
propertySpecBySlug.
|
|
71
|
+
const existingIds = propertySpecBySlug.get(slug);
|
|
72
|
+
propertySpecBySlug.set(slug, Array.isArray(existingIds) ? [...existingIds, ...idsArray] : idsArray);
|
|
72
73
|
continue;
|
|
73
74
|
}
|
|
74
75
|
const existing = propertySpecBySlug.get(slug);
|
|
@@ -190,6 +190,11 @@ describe('parsePropertySpecificationsIntoMap', () => {
|
|
|
190
190
|
expect(result.get('ids')).toEqual(['1', '2', '3']);
|
|
191
191
|
expect(result.get('name')).toBe('Test');
|
|
192
192
|
});
|
|
193
|
+
it('should accumulate repeated ids keys', () => {
|
|
194
|
+
const specs = ['ids=1,2', 'ids=3'];
|
|
195
|
+
const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
|
|
196
|
+
expect(result.get('ids')).toEqual(['1', '2', '3']);
|
|
197
|
+
});
|
|
193
198
|
it('should handle ids as single value', () => {
|
|
194
199
|
const specs = ['ids=123'];
|
|
195
200
|
const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.6.0-alpha-8-alpha.
|
|
2
|
+
"version": "3.6.0-alpha-8-alpha.828359d",
|
|
3
3
|
"commands": {
|
|
4
4
|
"app:create": {
|
|
5
5
|
"id": "app:create",
|
|
@@ -1077,7 +1077,7 @@
|
|
|
1077
1077
|
"name": "dryRun",
|
|
1078
1078
|
"type": "boolean",
|
|
1079
1079
|
"char": "d",
|
|
1080
|
-
"description": "If true, does not
|
|
1080
|
+
"description": "If true, does not update the entities but simulates the update",
|
|
1081
1081
|
"required": false,
|
|
1082
1082
|
"allowNo": false
|
|
1083
1083
|
},
|
|
@@ -1085,18 +1085,10 @@
|
|
|
1085
1085
|
"name": "inputFile",
|
|
1086
1086
|
"type": "option",
|
|
1087
1087
|
"char": "i",
|
|
1088
|
-
"description": "
|
|
1088
|
+
"description": "A file with a JSON array of {id, ...} objects; each object is one entity to update.",
|
|
1089
1089
|
"required": false,
|
|
1090
1090
|
"multiple": false
|
|
1091
1091
|
},
|
|
1092
|
-
"force": {
|
|
1093
|
-
"name": "force",
|
|
1094
|
-
"type": "boolean",
|
|
1095
|
-
"char": "F",
|
|
1096
|
-
"description": "Force update without validation. Internal use only",
|
|
1097
|
-
"required": false,
|
|
1098
|
-
"allowNo": false
|
|
1099
|
-
},
|
|
1100
1092
|
"suffix": {
|
|
1101
1093
|
"name": "suffix",
|
|
1102
1094
|
"type": "option",
|