@contrail/cli 3.6.0-alpha-8-alpha.de819d3 → 3.6.0-alpha-8-alpha.afa500a
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 +5 -4
- package/dist/commands/entities/create.js +5 -1
- package/dist/commands/entities/create.spec.js +2 -0
- package/dist/commands/entities/delete.js +17 -11
- package/dist/commands/entities/get.js +1 -10
- package/dist/commands/entities/update.d.ts +1 -0
- package/dist/commands/entities/update.js +12 -4
- package/dist/helpers/propertySpecifications/propertySpecifications.js +1 -2
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +0 -5
- package/oclif.manifest.json +11 -3
- 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.afa500a 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>] [-F]
|
|
690
690
|
[--suffix <value>] [--apiVersion 1|2]
|
|
691
691
|
|
|
692
692
|
ARGUMENTS
|
|
@@ -694,11 +694,12 @@ 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
|
|
697
698
|
-S, --skipHydrateRelationships Skip hydrating relationships when updating the entity
|
|
698
|
-
-d, --dryRun If true, does not
|
|
699
|
+
-d, --dryRun If true, does not create the entity but simulates the creation
|
|
699
700
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
700
701
|
<options: true|false>
|
|
701
|
-
-i, --inputFile=<value>
|
|
702
|
+
-i, --inputFile=<value> File containing properties to update the entity (optional)
|
|
702
703
|
-l, --logLevel=<option> The Log Level
|
|
703
704
|
<options: error|warn|info|debug>
|
|
704
705
|
--apiVersion=<option> Api-Version header to send; selects the V1 or V2 variant of the endpoint
|
|
@@ -290,6 +290,8 @@ 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.
|
|
293
295
|
function summarizeSettledCreates(inputs, settled) {
|
|
294
296
|
const created = [];
|
|
295
297
|
const failed = [];
|
|
@@ -536,7 +538,9 @@ function castValueToPropertyType(value, propertyType) {
|
|
|
536
538
|
if (Array.isArray(value)) {
|
|
537
539
|
return value;
|
|
538
540
|
}
|
|
539
|
-
// the old comma syntax parses as one
|
|
541
|
+
// Migration hazard: the old comma syntax parses as one string with a
|
|
542
|
+
// comma in it. Warn with the repeated-key equivalent instead of
|
|
543
|
+
// guessing at a split.
|
|
540
544
|
if (typeof value === 'string' && value.includes(',')) {
|
|
541
545
|
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
|
|
542
546
|
.split(',')
|
|
@@ -333,6 +333,8 @@ 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
|
|
336
338
|
const roles = [].concat(propertySpecs.get('roles'));
|
|
337
339
|
// Test that property level matching works with specified roles
|
|
338
340
|
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.idsErrored.length > 0) {
|
|
95
|
+
console.error(`${response.idsErrored.length} of ${ids.length} deletes failed.`);
|
|
96
96
|
process.exitCode = 1;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -103,26 +103,32 @@ 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 idsDeleted = new Set();
|
|
107
|
+
const idsNotFound = new Set();
|
|
108
|
+
const idsErrored = new Set();
|
|
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
|
+
idsNotFound.add(id);
|
|
113
|
+
return null;
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
idsDeleted.add(id);
|
|
116
|
+
return entity;
|
|
116
117
|
}, (error) => {
|
|
117
118
|
if (error.message.includes('not found')) {
|
|
118
|
-
|
|
119
|
+
idsNotFound.add(id);
|
|
119
120
|
}
|
|
120
121
|
else {
|
|
121
|
-
|
|
122
|
+
idsErrored.add(id);
|
|
122
123
|
}
|
|
124
|
+
return null;
|
|
123
125
|
})));
|
|
124
126
|
await Promise.all(deletePromises);
|
|
125
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
idsDeleted: Array.from(idsDeleted),
|
|
129
|
+
idsNotFound: Array.from(idsNotFound),
|
|
130
|
+
idsErrored: Array.from(idsErrored),
|
|
131
|
+
};
|
|
126
132
|
}
|
|
127
133
|
async function deleteEntityById(entityName, id, routing = {}) {
|
|
128
134
|
const client = new sdk_1.Entities();
|
|
@@ -157,9 +157,6 @@ 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
|
-
}
|
|
163
160
|
if (outputFile) {
|
|
164
161
|
fs_1.default.writeFileSync(outputFile, JSON.stringify(response, null, 2));
|
|
165
162
|
console.error(`Output saved to ${outputFile}`);
|
|
@@ -170,9 +167,6 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
170
167
|
}
|
|
171
168
|
}
|
|
172
169
|
catch (e) {
|
|
173
|
-
if (e.message?.startsWith('Entity not found:')) {
|
|
174
|
-
throw e;
|
|
175
|
-
}
|
|
176
170
|
this.error(`Error fetching entity: ${e.message}`);
|
|
177
171
|
}
|
|
178
172
|
}
|
|
@@ -188,8 +182,6 @@ async function getEntityByTypePath(typePath, propertySpec, options = {}) {
|
|
|
188
182
|
}
|
|
189
183
|
return find(entityName, {
|
|
190
184
|
id,
|
|
191
|
-
// a missing id returns null (no retries); the command turns it into a clean not-found error
|
|
192
|
-
ignore404: id !== undefined,
|
|
193
185
|
skipHydrateRelationships,
|
|
194
186
|
relations,
|
|
195
187
|
propertySlugs,
|
|
@@ -209,10 +201,9 @@ function convertPropertySpecIntoCriteria(propertySpec) {
|
|
|
209
201
|
}
|
|
210
202
|
async function find(entityName, options = {}) {
|
|
211
203
|
const client = new sdk_1.Entities();
|
|
212
|
-
const { criteria, id,
|
|
204
|
+
const { criteria, id, propertySlugs, skipHydrateRelationships, relations, skip, suffix, apiVersion, take, } = options;
|
|
213
205
|
const queryOptions = {
|
|
214
206
|
entityName,
|
|
215
|
-
ignore404,
|
|
216
207
|
skipHydrateRelationships,
|
|
217
208
|
relations,
|
|
218
209
|
skip,
|
|
@@ -6,6 +6,7 @@ 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>;
|
|
9
10
|
suffix: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
11
|
apiVersion: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
12
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
@@ -27,13 +27,18 @@ 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 create the entity but simulates the creation',
|
|
31
31
|
required: false,
|
|
32
32
|
default: false,
|
|
33
33
|
}),
|
|
34
34
|
inputFile: core_1.Flags.string({
|
|
35
35
|
char: 'i',
|
|
36
|
-
description: '
|
|
36
|
+
description: 'File containing properties to update the entity (optional)',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
force: core_1.Flags.boolean({
|
|
40
|
+
char: 'F',
|
|
41
|
+
description: 'Force update without validation. Internal use only',
|
|
37
42
|
required: false,
|
|
38
43
|
}),
|
|
39
44
|
suffix: core_1.Flags.string({
|
|
@@ -62,7 +67,7 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
62
67
|
await (0, login_1.authUser)();
|
|
63
68
|
const { args, argv } = await this.parse(UpdateEntity);
|
|
64
69
|
const { entityName } = args;
|
|
65
|
-
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, suffix, apiVersion, } = this.flags;
|
|
70
|
+
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, suffix, apiVersion, force, } = this.flags;
|
|
66
71
|
const { ids: positionalIds, specs } = (0, propertySpecifications_1.partitionIdsAndSpecs)(argv.slice(1));
|
|
67
72
|
const propertySpec = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
|
|
68
73
|
if (propertySpec.has('id') || propertySpec.has('ids')) {
|
|
@@ -96,6 +101,7 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
96
101
|
skipHydrateRelationships,
|
|
97
102
|
suffix,
|
|
98
103
|
apiVersion: apiVersion,
|
|
104
|
+
force,
|
|
99
105
|
});
|
|
100
106
|
}
|
|
101
107
|
try {
|
|
@@ -117,6 +123,8 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
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.
|
|
120
128
|
function summarizeSettledUpdates(inputs, settled, skipped) {
|
|
121
129
|
const updated = [];
|
|
122
130
|
const failed = [];
|
|
@@ -136,7 +144,7 @@ function summarizeSettledUpdates(inputs, settled, skipped) {
|
|
|
136
144
|
exports.summarizeSettledUpdates = summarizeSettledUpdates;
|
|
137
145
|
async function updateEntityById(entityName, id, changes, options = {}) {
|
|
138
146
|
const client = new sdk_1.Entities();
|
|
139
|
-
const { skipHydrateRelationships, suffix, apiVersion } = options;
|
|
147
|
+
const { skipHydrateRelationships, suffix, apiVersion, force } = options;
|
|
140
148
|
return client.update({
|
|
141
149
|
entityName,
|
|
142
150
|
id,
|
|
@@ -68,8 +68,7 @@ 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
|
-
|
|
72
|
-
propertySpecBySlug.set(slug, Array.isArray(existingIds) ? [...existingIds, ...idsArray] : idsArray);
|
|
71
|
+
propertySpecBySlug.set(slug, idsArray);
|
|
73
72
|
continue;
|
|
74
73
|
}
|
|
75
74
|
const existing = propertySpecBySlug.get(slug);
|
|
@@ -190,11 +190,6 @@ 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
|
-
});
|
|
198
193
|
it('should handle ids as single value', () => {
|
|
199
194
|
const specs = ['ids=123'];
|
|
200
195
|
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.afa500a",
|
|
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 create the entity but simulates the creation",
|
|
1081
1081
|
"required": false,
|
|
1082
1082
|
"allowNo": false
|
|
1083
1083
|
},
|
|
@@ -1085,10 +1085,18 @@
|
|
|
1085
1085
|
"name": "inputFile",
|
|
1086
1086
|
"type": "option",
|
|
1087
1087
|
"char": "i",
|
|
1088
|
-
"description": "
|
|
1088
|
+
"description": "File containing properties to update the entity (optional)",
|
|
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
|
+
},
|
|
1092
1100
|
"suffix": {
|
|
1093
1101
|
"name": "suffix",
|
|
1094
1102
|
"type": "option",
|