@contrail/cli 3.6.0-alpha-2 → 3.6.0-alpha-4
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 +12 -10
- package/dist/auth/login.js +15 -5
- package/dist/commands/config/set.js +1 -1
- package/dist/commands/entities/get.d.ts +3 -2
- package/dist/commands/entities/get.js +36 -22
- package/dist/commands/entities/update.d.ts +1 -0
- package/dist/commands/entities/update.js +28 -21
- package/dist/helpers/config/config.d.ts +4 -3
- package/dist/helpers/config/config.js +1 -0
- package/dist/helpers/entities/batchFetch.d.ts +2 -2
- package/dist/helpers/entities/batchFetch.js +9 -1
- package/dist/helpers/propertySpecifications/propertySpecifications.d.ts +4 -6
- package/dist/helpers/propertySpecifications/propertySpecifications.js +3 -10
- package/dist/helpers/propertySpecifications/propertySpecifications.spec.js +4 -64
- package/oclif.manifest.json +37 -20
- 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-
|
|
20
|
+
@contrail/cli/3.6.0-alpha-4 darwin-arm64 node-v22.19.0
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
|
@@ -54,7 +54,7 @@ To get instructions on how to set up autocomplete for your shell, run `contrail
|
|
|
54
54
|
* [`contrail entities create PATH`](#contrail-entities-create-path)
|
|
55
55
|
* [`contrail entities delete ENTITYNAME`](#contrail-entities-delete-entityname)
|
|
56
56
|
* [`contrail entities get TYPEPATH`](#contrail-entities-get-typepath)
|
|
57
|
-
* [`contrail entities update ENTITYNAME ID`](#contrail-entities-update-entityname-id)
|
|
57
|
+
* [`contrail entities update ENTITYNAME [ID]`](#contrail-entities-update-entityname-id)
|
|
58
58
|
* [`contrail file upload FILELOCATION CONTENTTYPE`](#contrail-file-upload-filelocation-contenttype)
|
|
59
59
|
* [`contrail groups export`](#contrail-groups-export)
|
|
60
60
|
* [`contrail groups import GROUPSFILE`](#contrail-groups-import-groupsfile)
|
|
@@ -495,7 +495,7 @@ Set a variable in the configuration
|
|
|
495
495
|
```
|
|
496
496
|
USAGE
|
|
497
497
|
$ contrail config set [-l error|warn|info|debug] [-e true|false] [--orgSlug <value>] [--environment
|
|
498
|
-
dev|development|prod|production] [--apiUserToken <value>] [--config <value>]
|
|
498
|
+
dev|development|prod|production|local] [--apiUserToken <value>] [--config <value>]
|
|
499
499
|
|
|
500
500
|
FLAGS
|
|
501
501
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
@@ -505,7 +505,7 @@ FLAGS
|
|
|
505
505
|
--apiUserToken=<value> The API User Token
|
|
506
506
|
--config=<value> The config name
|
|
507
507
|
--environment=<option> The Environment
|
|
508
|
-
<options: dev|development|prod|production>
|
|
508
|
+
<options: dev|development|prod|production|local>
|
|
509
509
|
--orgSlug=<value> The org slug
|
|
510
510
|
|
|
511
511
|
DESCRIPTION
|
|
@@ -625,14 +625,15 @@ EXAMPLES
|
|
|
625
625
|
|
|
626
626
|
```
|
|
627
627
|
USAGE
|
|
628
|
-
$ contrail entities get TYPEPATH [-l error|warn|info|debug] [-e true|false] [-o <value>] [-
|
|
629
|
-
<value>] [-
|
|
628
|
+
$ contrail entities get TYPEPATH [-l error|warn|info|debug] [-e true|false] [-o <value>] [-p <value>] [-P] [-s
|
|
629
|
+
<value>] [-S] [-t <value>] [-r <value>]
|
|
630
630
|
|
|
631
631
|
ARGUMENTS
|
|
632
632
|
TYPEPATH The name of the entity type to fetch
|
|
633
633
|
|
|
634
634
|
FLAGS
|
|
635
635
|
-P, --paginated If true, fetches all entities in a paginated manner
|
|
636
|
+
-S, --skipHydrateRelationships Skip hydrating relationships when fetching the entity
|
|
636
637
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
637
638
|
<options: true|false>
|
|
638
639
|
-l, --logLevel=<option> The Log Level
|
|
@@ -640,7 +641,7 @@ FLAGS
|
|
|
640
641
|
-o, --outputFile=<value> File to save the output (optional)
|
|
641
642
|
-p, --propertySlugs=<value> Comma-separated list of property slugs to include in the output
|
|
642
643
|
-r, --relations=<value> Comma-separated list of relations to include in the output
|
|
643
|
-
-s, --
|
|
644
|
+
-s, --skip=<value> Number of entities to skip (used with paginated fetch)
|
|
644
645
|
-t, --take=<value> Number of entities to fetch in a single request (default is 100)
|
|
645
646
|
|
|
646
647
|
DESCRIPTION
|
|
@@ -656,19 +657,20 @@ EXAMPLES
|
|
|
656
657
|
$ contrail entities get events -- timestamp=..2025-09-16T23:59:59Z
|
|
657
658
|
```
|
|
658
659
|
|
|
659
|
-
## `contrail entities update ENTITYNAME ID`
|
|
660
|
+
## `contrail entities update ENTITYNAME [ID]`
|
|
660
661
|
|
|
661
662
|
(BETA) Update an entity
|
|
662
663
|
|
|
663
664
|
```
|
|
664
665
|
USAGE
|
|
665
|
-
$ contrail entities update ENTITYNAME ID [-l error|warn|info|debug] [-e true|false] [-s] [-d] [-i <value>]
|
|
666
|
+
$ contrail entities update ENTITYNAME [ID] [-l error|warn|info|debug] [-e true|false] [-s] [-d] [-i <value>] [-F]
|
|
666
667
|
|
|
667
668
|
ARGUMENTS
|
|
668
669
|
ENTITYNAME The name of the entity type to update
|
|
669
670
|
ID The unique identifier of the entity
|
|
670
671
|
|
|
671
672
|
FLAGS
|
|
673
|
+
-F, --force Force update without validation. Internal use only
|
|
672
674
|
-d, --dryRun If true, does not create the entity but simulates the creation
|
|
673
675
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
674
676
|
<options: true|false>
|
|
@@ -683,7 +685,7 @@ DESCRIPTION
|
|
|
683
685
|
EXAMPLES
|
|
684
686
|
$ contrail entities update color 123 -- name=obsidian hex=000000
|
|
685
687
|
|
|
686
|
-
$ contrail entities update color 123 -- description= notes='""'
|
|
688
|
+
$ contrail entities update color 123 -- description= notes='""' # Unset description, set notes to empty string
|
|
687
689
|
```
|
|
688
690
|
|
|
689
691
|
## `contrail file upload FILELOCATION CONTENTTYPE`
|
package/dist/auth/login.js
CHANGED
|
@@ -55,7 +55,9 @@ async function authUser() {
|
|
|
55
55
|
logger_1.default.info(`Failed to refresh token: ${e}. Requesting auth credentials...`);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
const authContext = await authenticateWithUsernamePassword(config);
|
|
59
|
+
await (0, config_1.storeConfig)(authContext);
|
|
60
|
+
return authContext;
|
|
59
61
|
}
|
|
60
62
|
exports.authUser = authUser;
|
|
61
63
|
async function getOrgSlug(config) {
|
|
@@ -77,7 +79,7 @@ async function authenticateWithApiKey(config, apiKey, orgSlug) {
|
|
|
77
79
|
const apiGateway = config?.apiGateway ?? CONFIGURATION.apiGateway;
|
|
78
80
|
sdk_1.Auth.config = {
|
|
79
81
|
apiUserToken: apiKey,
|
|
80
|
-
apiRefreshToken:
|
|
82
|
+
apiRefreshToken: undefined,
|
|
81
83
|
apiGateway,
|
|
82
84
|
orgSlug,
|
|
83
85
|
};
|
|
@@ -145,20 +147,25 @@ async function authenticateWithUsernamePassword(config) {
|
|
|
145
147
|
async function setAuthContext(user, params) {
|
|
146
148
|
const apiGateway = params?.apiGateway ?? CONFIGURATION.apiGateway;
|
|
147
149
|
const { vibeAccessToken, vibeRefreshToken } = await getVibeAccessTokenFromCognitoAccessToken(user.idToken.jwtToken, apiGateway);
|
|
148
|
-
// Set Auth Config
|
|
149
150
|
sdk_1.Auth.config = {
|
|
150
151
|
apiUserToken: vibeAccessToken,
|
|
151
152
|
apiRefreshToken: vibeRefreshToken,
|
|
152
153
|
apiGateway,
|
|
153
154
|
orgSlug: params.orgSlug,
|
|
154
155
|
};
|
|
155
|
-
// SET SDK CONFIG
|
|
156
156
|
(0, sdk_1.setConfig)(sdk_1.Auth.config);
|
|
157
157
|
return refreshCurrentUserOrg();
|
|
158
158
|
}
|
|
159
159
|
async function refreshCurrentUserOrg() {
|
|
160
160
|
logger_1.default.debug('refreshing current user org...');
|
|
161
|
-
const currentUser = await (0, sdk_1.getCurrentUser)()
|
|
161
|
+
const currentUser = await (0, sdk_1.getCurrentUser)().catch((e) => {
|
|
162
|
+
console.error('error getting current user', e.message);
|
|
163
|
+
if (isExpiredTokenError(e)) {
|
|
164
|
+
console.log('try frefreshing');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw e;
|
|
168
|
+
});
|
|
162
169
|
if (!currentUser) {
|
|
163
170
|
const config = await (0, config_1.getConfig)();
|
|
164
171
|
try {
|
|
@@ -181,6 +188,9 @@ async function refreshCurrentUserOrg() {
|
|
|
181
188
|
// await checkTelemetry(current_Version);
|
|
182
189
|
return Object.assign({}, sdk_1.Auth.config);
|
|
183
190
|
}
|
|
191
|
+
function isExpiredTokenError(e) {
|
|
192
|
+
return e?.message?.includes('token is expired');
|
|
193
|
+
}
|
|
184
194
|
async function getVibeAccessTokenFromCognitoAccessToken(cognitoAccessToken, apiGateway) {
|
|
185
195
|
const url = `${apiGateway}/auth/cognito/`;
|
|
186
196
|
const response = await fetch(url, {
|
|
@@ -15,7 +15,7 @@ class SetConfig extends basecommand_1.BaseCommand {
|
|
|
15
15
|
environment: core_1.Flags.string({
|
|
16
16
|
description: 'The Environment',
|
|
17
17
|
required: false,
|
|
18
|
-
options: ['dev', 'development', 'prod', 'production'],
|
|
18
|
+
options: ['dev', 'development', 'prod', 'production', 'local'],
|
|
19
19
|
}),
|
|
20
20
|
apiUserToken: core_1.Flags.string({
|
|
21
21
|
description: 'The API User Token',
|
|
@@ -4,11 +4,12 @@ export default class GetEntity extends BaseCommand {
|
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
outputFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
-
skipHydrateRelationships: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
-
relations: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
7
|
propertySlugs: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
8
|
paginated: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
skip: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
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
|
+
relations: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
13
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
13
14
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
14
15
|
};
|
|
@@ -8,6 +8,7 @@ const sdk_1 = require("@contrail/sdk");
|
|
|
8
8
|
const login_1 = require("../../auth/login");
|
|
9
9
|
const types_1 = require("../../api/types");
|
|
10
10
|
const propertySpecifications_1 = require("../../helpers/propertySpecifications/propertySpecifications");
|
|
11
|
+
const batchFetch_1 = require("../../helpers/entities/batchFetch");
|
|
11
12
|
let SHOULD_USE_PAGINATED_FIND = false;
|
|
12
13
|
let MAX_NUMBER_OF_ENTITIES_TO_FETCH = undefined;
|
|
13
14
|
class GetEntity extends basecommand_1.BaseCommand {
|
|
@@ -25,17 +26,6 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
25
26
|
description: 'File to save the output (optional)',
|
|
26
27
|
required: false,
|
|
27
28
|
}),
|
|
28
|
-
skipHydrateRelationships: core_1.Flags.boolean({
|
|
29
|
-
char: 's',
|
|
30
|
-
description: 'Skip hydrating relationships when fetching the entity',
|
|
31
|
-
required: false,
|
|
32
|
-
default: false,
|
|
33
|
-
}),
|
|
34
|
-
relations: core_1.Flags.string({
|
|
35
|
-
char: 'r',
|
|
36
|
-
description: 'Comma-separated list of relations to include in the output',
|
|
37
|
-
required: false,
|
|
38
|
-
}),
|
|
39
29
|
propertySlugs: core_1.Flags.string({
|
|
40
30
|
char: 'p',
|
|
41
31
|
description: 'Comma-separated list of property slugs to include in the output',
|
|
@@ -47,12 +37,28 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
47
37
|
required: false,
|
|
48
38
|
default: false,
|
|
49
39
|
}),
|
|
40
|
+
skip: core_1.Flags.integer({
|
|
41
|
+
char: 's',
|
|
42
|
+
description: 'Number of entities to skip (used with paginated fetch)',
|
|
43
|
+
required: false,
|
|
44
|
+
}),
|
|
45
|
+
skipHydrateRelationships: core_1.Flags.boolean({
|
|
46
|
+
char: 'S',
|
|
47
|
+
description: 'Skip hydrating relationships when fetching the entity',
|
|
48
|
+
required: false,
|
|
49
|
+
default: false,
|
|
50
|
+
}),
|
|
50
51
|
take: core_1.Flags.integer({
|
|
51
52
|
char: 't',
|
|
52
53
|
description: 'Number of entities to fetch in a single request (default is 100)',
|
|
53
54
|
required: false,
|
|
54
55
|
default: MAX_NUMBER_OF_ENTITIES_TO_FETCH,
|
|
55
56
|
}),
|
|
57
|
+
relations: core_1.Flags.string({
|
|
58
|
+
char: 'r',
|
|
59
|
+
description: 'Comma-separated list of relations to include in the output',
|
|
60
|
+
required: false,
|
|
61
|
+
}),
|
|
56
62
|
};
|
|
57
63
|
static args = {
|
|
58
64
|
typePath: core_1.Args.string({
|
|
@@ -66,9 +72,9 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
66
72
|
await (0, login_1.authUser)();
|
|
67
73
|
const { args, flags, argv } = await this.parse(GetEntity);
|
|
68
74
|
const { typePath } = args;
|
|
69
|
-
const { outputFile, propertySlugs, relations: relationString,
|
|
75
|
+
const { outputFile, paginated: shouldUsePaginatedFind, propertySlugs, relations: relationString, skip, skipHydrateRelationships, take, } = flags;
|
|
70
76
|
SHOULD_USE_PAGINATED_FIND = shouldUsePaginatedFind;
|
|
71
|
-
MAX_NUMBER_OF_ENTITIES_TO_FETCH =
|
|
77
|
+
MAX_NUMBER_OF_ENTITIES_TO_FETCH = take;
|
|
72
78
|
if (MAX_NUMBER_OF_ENTITIES_TO_FETCH)
|
|
73
79
|
console.log(`Will limit output to ${MAX_NUMBER_OF_ENTITIES_TO_FETCH} entities.`);
|
|
74
80
|
const propertySpecificationArgs = argv
|
|
@@ -92,10 +98,11 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
92
98
|
// thinks it is hard to read. Consider breaking it into smaller functions.
|
|
93
99
|
if (SHOULD_USE_PAGINATED_FIND) {
|
|
94
100
|
const stream = streamPaginatedFind(typePath, {
|
|
101
|
+
criteria: convertPropertySpecIntoCriteria(propertySpecBySlug),
|
|
95
102
|
skipHydrateRelationships,
|
|
103
|
+
skip,
|
|
96
104
|
relations,
|
|
97
105
|
propertySlugs: propertySlugsArray,
|
|
98
|
-
criteria: convertPropertySpecIntoCriteria(propertySpecBySlug),
|
|
99
106
|
});
|
|
100
107
|
const writer = outputFile ? fs_1.default.createWriteStream(outputFile) : process.stdout;
|
|
101
108
|
writer.write('[\n');
|
|
@@ -115,6 +122,8 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
115
122
|
skipHydrateRelationships,
|
|
116
123
|
relations,
|
|
117
124
|
propertySlugs: propertySlugsArray,
|
|
125
|
+
skip,
|
|
126
|
+
take,
|
|
118
127
|
});
|
|
119
128
|
if (outputFile) {
|
|
120
129
|
fs_1.default.writeFileSync(outputFile, JSON.stringify(response, null, 2));
|
|
@@ -132,17 +141,14 @@ class GetEntity extends basecommand_1.BaseCommand {
|
|
|
132
141
|
}
|
|
133
142
|
exports.default = GetEntity;
|
|
134
143
|
async function getEntityByTypePath(typePath, propertySpec, options = {}) {
|
|
135
|
-
const { propertySlugs, relations, skipHydrateRelationships } = options;
|
|
144
|
+
const { propertySlugs, relations, skipHydrateRelationships, skip, take } = options;
|
|
136
145
|
const id = propertySpec?.get('id')?.toString() || undefined;
|
|
146
|
+
const criteria = convertPropertySpecIntoCriteria(propertySpec || new Map());
|
|
137
147
|
const { root: entityName } = (0, types_1.parseTypePath)(typePath);
|
|
138
|
-
let criteria = {};
|
|
139
148
|
if (entityName === 'custom-entity' && !id) {
|
|
140
149
|
criteria.typePath = typePath;
|
|
141
150
|
}
|
|
142
|
-
|
|
143
|
-
criteria = convertPropertySpecIntoCriteria(propertySpec || new Map());
|
|
144
|
-
}
|
|
145
|
-
return find(entityName, { id, skipHydrateRelationships, relations, propertySlugs, criteria });
|
|
151
|
+
return find(entityName, { id, skipHydrateRelationships, relations, propertySlugs, criteria, skip, take });
|
|
146
152
|
}
|
|
147
153
|
function convertPropertySpecIntoCriteria(propertySpec) {
|
|
148
154
|
const criteria = {};
|
|
@@ -153,16 +159,24 @@ function convertPropertySpecIntoCriteria(propertySpec) {
|
|
|
153
159
|
}
|
|
154
160
|
async function find(entityName, options = {}) {
|
|
155
161
|
const client = new sdk_1.Entities();
|
|
156
|
-
const { criteria, id, propertySlugs, skipHydrateRelationships, relations } = options;
|
|
162
|
+
const { criteria, id, propertySlugs, skipHydrateRelationships, relations, skip, take } = options;
|
|
157
163
|
const queryOptions = {
|
|
158
164
|
entityName,
|
|
159
165
|
skipHydrateRelationships,
|
|
160
166
|
relations,
|
|
167
|
+
skip,
|
|
168
|
+
take
|
|
161
169
|
};
|
|
162
170
|
if (propertySlugs && propertySlugs.length > 0) {
|
|
163
171
|
queryOptions.customParams = { propertySlugs: propertySlugs.join(',') };
|
|
164
172
|
}
|
|
165
|
-
|
|
173
|
+
const payload = id ? { id, ...queryOptions } : { criteria, ...queryOptions };
|
|
174
|
+
if ((0, batchFetch_1.canBatchFetchEntity)(entityName)) {
|
|
175
|
+
return (0, batchFetch_1.batchFetch)(entityName, payload);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
return client.get(payload);
|
|
179
|
+
}
|
|
166
180
|
}
|
|
167
181
|
async function* streamPaginatedFind(typePath, options = {}) {
|
|
168
182
|
const client = new sdk_1.Entities();
|
|
@@ -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
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
11
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
12
|
};
|
|
@@ -12,7 +12,7 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
12
12
|
static description = '(BETA) Update an entity';
|
|
13
13
|
static examples = [
|
|
14
14
|
'contrail entities update color 123 -- name=obsidian hex=000000',
|
|
15
|
-
'contrail entities update color 123 -- description= notes=\'""\'
|
|
15
|
+
'contrail entities update color 123 -- description= notes=\'""\' # Unset description, set notes to empty string',
|
|
16
16
|
];
|
|
17
17
|
static flags = {
|
|
18
18
|
...basecommand_1.BaseCommand.baseFlags,
|
|
@@ -33,6 +33,11 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
33
33
|
description: 'File containing properties to update the entity (optional)',
|
|
34
34
|
required: false,
|
|
35
35
|
}),
|
|
36
|
+
force: core_1.Flags.boolean({
|
|
37
|
+
char: 'F',
|
|
38
|
+
description: 'Force update without validation. Internal use only',
|
|
39
|
+
required: false,
|
|
40
|
+
})
|
|
36
41
|
};
|
|
37
42
|
static args = {
|
|
38
43
|
entityName: core_1.Args.string({
|
|
@@ -41,7 +46,7 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
41
46
|
}),
|
|
42
47
|
id: core_1.Args.string({
|
|
43
48
|
description: 'The unique identifier of the entity',
|
|
44
|
-
required:
|
|
49
|
+
required: false,
|
|
45
50
|
}),
|
|
46
51
|
};
|
|
47
52
|
static strict = false;
|
|
@@ -49,34 +54,34 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
49
54
|
this.log('🚨 This command is in BETA and may change or be removed at any time.');
|
|
50
55
|
await (0, login_1.authUser)();
|
|
51
56
|
const { args, argv } = await this.parse(UpdateEntity);
|
|
52
|
-
const { id, entityName } = args;
|
|
53
|
-
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships } = this.flags;
|
|
57
|
+
const { id: specifiedId, entityName } = args;
|
|
58
|
+
const { dryRun: isDryRun, inputFile: inputFilename, skipHydrateRelationships, force } = this.flags;
|
|
54
59
|
const propertySpecArgs = argv.slice(1, argv.length).filter(propertySpecifications_1.isValidPropertySpecification);
|
|
55
60
|
const propertySpec = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(propertySpecArgs);
|
|
56
|
-
if (
|
|
57
|
-
throw new Error('No valid properties provided for update');
|
|
58
|
-
}
|
|
59
|
-
if (inputFilename && id) {
|
|
61
|
+
if (inputFilename && specifiedId) {
|
|
60
62
|
throw new Error('You cannot use --input and --id together. Please specify one or the other.');
|
|
61
63
|
}
|
|
62
|
-
const
|
|
63
|
-
? (0, file_helpers_1.parseFileAsJsonArray)(inputFilename)
|
|
64
|
-
: [{ id }];
|
|
64
|
+
const entitiesToUpdate = inputFilename
|
|
65
|
+
? (0, file_helpers_1.parseFileAsJsonArray)(inputFilename)
|
|
66
|
+
: [{ id: specifiedId }];
|
|
65
67
|
const results = {
|
|
66
68
|
idsSuccessfullyUpdated: new Set(),
|
|
67
69
|
idsFailedToUpdate: new Set(),
|
|
68
|
-
numEntriesSkipped:
|
|
70
|
+
numEntriesSkipped: entitiesToUpdate.filter(({ id }) => !id).length,
|
|
69
71
|
};
|
|
70
|
-
const
|
|
72
|
+
const entitiesWithId = entitiesToUpdate.filter(({ id }) => id);
|
|
71
73
|
propertySpec.delete('id'); // Remove 'id' from the property spec if it exists
|
|
72
|
-
|
|
74
|
+
// will be applied to all entities
|
|
75
|
+
const fixedValues = [...propertySpec.entries()].reduce((acc, [key, value]) => {
|
|
73
76
|
acc[key] = value;
|
|
74
77
|
return acc;
|
|
75
78
|
}, {});
|
|
76
|
-
async function updateEntity(
|
|
79
|
+
async function updateEntity(entity) {
|
|
80
|
+
const newEntityState = { ...entity, ...fixedValues };
|
|
81
|
+
const id = entity.id;
|
|
77
82
|
const promise = isDryRun
|
|
78
|
-
? mockUpdateById(entityName, id,
|
|
79
|
-
: updateEntityById(entityName, id,
|
|
83
|
+
? mockUpdateById(entityName, id, newEntityState, { skipHydrateRelationships })
|
|
84
|
+
: updateEntityById(entityName, id, newEntityState, { skipHydrateRelationships, force });
|
|
80
85
|
promise
|
|
81
86
|
.catch(() => {
|
|
82
87
|
results.idsFailedToUpdate.add(id);
|
|
@@ -87,8 +92,8 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
87
92
|
return promise;
|
|
88
93
|
}
|
|
89
94
|
try {
|
|
90
|
-
const limit = (0, p_limit_1.default)(
|
|
91
|
-
const responses = await Promise.all(
|
|
95
|
+
const limit = (0, p_limit_1.default)(20);
|
|
96
|
+
const responses = await Promise.all(entitiesWithId.map((entity) => limit(() => updateEntity(entity))));
|
|
92
97
|
const messagePrefix = isDryRun
|
|
93
98
|
? 'Dry run. Update skipped. Changes would be:'
|
|
94
99
|
: 'Entity updated successfully:';
|
|
@@ -105,8 +110,10 @@ class UpdateEntity extends basecommand_1.BaseCommand {
|
|
|
105
110
|
exports.default = UpdateEntity;
|
|
106
111
|
async function updateEntityById(entityName, id, changes, options = {}) {
|
|
107
112
|
const client = new sdk_1.Entities();
|
|
108
|
-
const { skipHydrateRelationships } = options;
|
|
109
|
-
|
|
113
|
+
const { skipHydrateRelationships, force } = options;
|
|
114
|
+
const fullPath = force ? `maintenance/items/${id}` : undefined;
|
|
115
|
+
console.log(fullPath);
|
|
116
|
+
return client.update({ entityName, id, object: changes, skipHydrateRelationships, fullPath });
|
|
110
117
|
}
|
|
111
118
|
async function mockUpdateById(entityName, id, changes, options = {}) {
|
|
112
119
|
const client = new sdk_1.Entities();
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
export declare const ENVIRONMENTS: {
|
|
2
2
|
dev: string;
|
|
3
3
|
prod: string;
|
|
4
|
+
local: string;
|
|
4
5
|
};
|
|
5
6
|
export declare function initializeVibeIQConfig(): Promise<void>;
|
|
6
7
|
export declare function readVibeIQConfig(): Promise<any>;
|
|
7
8
|
export declare function writeVibeIQConfig(config: any): Promise<void>;
|
|
8
9
|
export declare function storeVibeIQConfig(incomingConfig: any): Promise<void>;
|
|
9
10
|
export declare function createConfig(fileName: any): Promise<void>;
|
|
10
|
-
export declare function writeConfig(config: any, configName?: string): Promise<void>;
|
|
11
|
+
export declare function writeConfig(config: any, configName?: string | null): Promise<void>;
|
|
11
12
|
export declare function getConfig(): Promise<any>;
|
|
12
|
-
export declare function storeConfig(incomingConfig: any, configName?: string, merge?: boolean): Promise<void>;
|
|
13
|
+
export declare function storeConfig(incomingConfig: any, configName?: string | null, merge?: boolean): Promise<void>;
|
|
13
14
|
export declare function getConfigFiles(): Promise<any>;
|
|
14
15
|
export declare function getConfigContent(fileName: string): Promise<any>;
|
|
15
|
-
export declare function validateEnvironments(envName?:
|
|
16
|
+
export declare function validateEnvironments(envName?: string | null): Promise<any>;
|
|
16
17
|
export declare function checkTelemetry(cliVersion: string): Promise<void>;
|
|
@@ -15,6 +15,7 @@ const defaultConfigFileLocation = (0, node_path_1.join)(configDirectoryLocation,
|
|
|
15
15
|
exports.ENVIRONMENTS = {
|
|
16
16
|
dev: 'https://api.dev.vibeiq.com/dev/api',
|
|
17
17
|
prod: 'https://api.vibeiq.com/prod/api',
|
|
18
|
+
local: 'http://localhost:3000/api',
|
|
18
19
|
};
|
|
19
20
|
async function initializeVibeIQConfig() {
|
|
20
21
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AssortmentItem,
|
|
1
|
+
import { AssortmentItem, ProjectItem } from '@contrail/entity-types';
|
|
2
2
|
import { EntitiesClientGetOptions } from '@contrail/sdk';
|
|
3
|
+
export declare function canBatchFetchEntity(entityName: string): entityName is keyof BatchFetchableEntityMap;
|
|
3
4
|
export interface BatchFetchableEntityMap {
|
|
4
|
-
item: Item;
|
|
5
5
|
'project-item': ProjectItem;
|
|
6
6
|
'assortment-item': AssortmentItem;
|
|
7
7
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.batchFetch = void 0;
|
|
3
|
+
exports.batchFetch = exports.canBatchFetchEntity = void 0;
|
|
4
4
|
const sdk_1 = require("@contrail/sdk");
|
|
5
5
|
const util_1 = require("@contrail/util");
|
|
6
|
+
// Maps entity names to their types for batch fetching.
|
|
7
|
+
// This allows for type-safe batch fetching of entities
|
|
8
|
+
// such that the Type returned always matches the entity name.
|
|
9
|
+
const BATCH_FETCHABLE_ENTITIES = ['assortment-item', 'project-item'];
|
|
10
|
+
function canBatchFetchEntity(entityName) {
|
|
11
|
+
return BATCH_FETCHABLE_ENTITIES.includes(entityName);
|
|
12
|
+
}
|
|
13
|
+
exports.canBatchFetchEntity = canBatchFetchEntity;
|
|
6
14
|
async function batchFetch(entityName, options) {
|
|
7
15
|
const response = await requestBatchFetch(entityName, options);
|
|
8
16
|
return parseBatchFetchResponse(response);
|
|
@@ -10,24 +10,22 @@
|
|
|
10
10
|
*
|
|
11
11
|
* It is typically used as the argv in a command.
|
|
12
12
|
*
|
|
13
|
-
* Special handling for empty values
|
|
13
|
+
* Special handling for empty values:
|
|
14
14
|
* - slug= (empty value) → property is set to undefined (unset)
|
|
15
15
|
* - slug='""' or slug="''" → property is set to empty string ""
|
|
16
|
-
* - slug=null → property is set to null
|
|
17
|
-
* - slug="null" → property is set to the string "null"
|
|
18
16
|
*
|
|
19
17
|
* Examples:
|
|
20
18
|
*
|
|
21
19
|
* contrail entities get color -- id=123
|
|
22
20
|
* contrail entities update color 123 -- name=obsidian hex=000000
|
|
23
|
-
* contrail entities update color 123 -- description= notes='""'
|
|
21
|
+
* contrail entities update color 123 -- description= notes='""'
|
|
24
22
|
* contrail entities get loader-processes -- createdOn=2025-09-15..2025-09-16
|
|
25
23
|
*/
|
|
26
24
|
export type PropertySpecification = {
|
|
27
25
|
slug: string;
|
|
28
|
-
value: string | undefined
|
|
26
|
+
value: string | undefined;
|
|
29
27
|
};
|
|
30
|
-
export type PropertySpecificationBySlug = Map<string, string | number | string[] | number[] | undefined
|
|
28
|
+
export type PropertySpecificationBySlug = Map<string, string | number | string[] | number[] | undefined>;
|
|
31
29
|
export type PropertySpecificationString = `${string}=${string}`;
|
|
32
30
|
export declare function parsePropertySpecification(spec: PropertySpecificationString): PropertySpecification;
|
|
33
31
|
export declare function parsePropertySpecificationsIntoMap(propertySpecifications: PropertySpecificationString[]): PropertySpecificationBySlug;
|
|
@@ -11,17 +11,15 @@
|
|
|
11
11
|
*
|
|
12
12
|
* It is typically used as the argv in a command.
|
|
13
13
|
*
|
|
14
|
-
* Special handling for empty values
|
|
14
|
+
* Special handling for empty values:
|
|
15
15
|
* - slug= (empty value) → property is set to undefined (unset)
|
|
16
16
|
* - slug='""' or slug="''" → property is set to empty string ""
|
|
17
|
-
* - slug=null → property is set to null
|
|
18
|
-
* - slug="null" → property is set to the string "null"
|
|
19
17
|
*
|
|
20
18
|
* Examples:
|
|
21
19
|
*
|
|
22
20
|
* contrail entities get color -- id=123
|
|
23
21
|
* contrail entities update color 123 -- name=obsidian hex=000000
|
|
24
|
-
* contrail entities update color 123 -- description= notes='""'
|
|
22
|
+
* contrail entities update color 123 -- description= notes='""'
|
|
25
23
|
* contrail entities get loader-processes -- createdOn=2025-09-15..2025-09-16
|
|
26
24
|
*/
|
|
27
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -40,11 +38,9 @@ function parsePropertySpecification(spec) {
|
|
|
40
38
|
if (!slug) {
|
|
41
39
|
throw new Error(`Invalid property specification: ${spec}`);
|
|
42
40
|
}
|
|
43
|
-
// Handle special cases for empty strings
|
|
41
|
+
// Handle special cases for empty strings:
|
|
44
42
|
// - x= means undefined (unset the property)
|
|
45
43
|
// - x='""' or x="''" means empty string ""
|
|
46
|
-
// - x=null means null value
|
|
47
|
-
// - x="null" means the string "null"
|
|
48
44
|
let finalValue;
|
|
49
45
|
if (value === '') {
|
|
50
46
|
finalValue = undefined;
|
|
@@ -52,9 +48,6 @@ function parsePropertySpecification(spec) {
|
|
|
52
48
|
else if (value === DOUBLE_QUOTED_EMPTY || value === SINGLE_QUOTED_EMPTY) {
|
|
53
49
|
finalValue = '';
|
|
54
50
|
}
|
|
55
|
-
else if (value === 'null') {
|
|
56
|
-
finalValue = null;
|
|
57
|
-
}
|
|
58
51
|
else {
|
|
59
52
|
finalValue = value;
|
|
60
53
|
}
|
|
@@ -78,28 +78,6 @@ describe('parsePropertySpecification', () => {
|
|
|
78
78
|
value: '',
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
|
-
it('should parse property specification with null value', () => {
|
|
82
|
-
const spec = 'description=null';
|
|
83
|
-
const result = (0, propertySpecifications_1.parsePropertySpecification)(spec);
|
|
84
|
-
expect(result).toEqual({
|
|
85
|
-
slug: 'description',
|
|
86
|
-
value: null,
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
it('should distinguish between null and the string "null"', () => {
|
|
90
|
-
const nullSpec = 'field=null';
|
|
91
|
-
const stringNullSpec = 'field="null"';
|
|
92
|
-
const nullResult = (0, propertySpecifications_1.parsePropertySpecification)(nullSpec);
|
|
93
|
-
const stringNullResult = (0, propertySpecifications_1.parsePropertySpecification)(stringNullSpec);
|
|
94
|
-
expect(nullResult).toEqual({
|
|
95
|
-
slug: 'field',
|
|
96
|
-
value: null,
|
|
97
|
-
});
|
|
98
|
-
expect(stringNullResult).toEqual({
|
|
99
|
-
slug: 'field',
|
|
100
|
-
value: '"null"',
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
81
|
it('should throw error for spec without equals sign', () => {
|
|
104
82
|
const spec = 'invalidspec';
|
|
105
83
|
expect(() => (0, propertySpecifications_1.parsePropertySpecification)(spec)).toThrow('Invalid property specification: invalidspec');
|
|
@@ -175,23 +153,9 @@ describe('parsePropertySpecificationsIntoMap', () => {
|
|
|
175
153
|
const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
|
|
176
154
|
expect(result.get('name')).toBe('john');
|
|
177
155
|
expect(result.get('description')).toBeUndefined();
|
|
178
|
-
expect(result.get('notes')).toBe('');
|
|
156
|
+
expect(result.get('notes')).toBe(''); // Empty quotes are converted to empty string
|
|
179
157
|
expect(result.size).toBe(3);
|
|
180
158
|
});
|
|
181
|
-
it('should handle property specifications with null values', () => {
|
|
182
|
-
const specs = [
|
|
183
|
-
'name=john',
|
|
184
|
-
'description=null',
|
|
185
|
-
'notes="null"',
|
|
186
|
-
'empty=',
|
|
187
|
-
];
|
|
188
|
-
const result = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(specs);
|
|
189
|
-
expect(result.get('name')).toBe('john');
|
|
190
|
-
expect(result.get('description')).toBeNull();
|
|
191
|
-
expect(result.get('notes')).toBe('"null"');
|
|
192
|
-
expect(result.get('empty')).toBeUndefined();
|
|
193
|
-
expect(result.size).toBe(4);
|
|
194
|
-
});
|
|
195
159
|
it('should throw error for invalid property specifications', () => {
|
|
196
160
|
const specs = [
|
|
197
161
|
'name=john',
|
|
@@ -278,21 +242,13 @@ describe('integration tests', () => {
|
|
|
278
242
|
const quotedSpecs = ['description=""'];
|
|
279
243
|
const quotedMap = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(quotedSpecs);
|
|
280
244
|
expect(quotedMap.get('description')).toBe(''); // Quoted empty = empty string
|
|
281
|
-
// Test case 3: Mixed usage
|
|
282
|
-
const mixedSpecs = [
|
|
283
|
-
'name=test',
|
|
284
|
-
'description=',
|
|
285
|
-
'notes=""',
|
|
286
|
-
'data=null',
|
|
287
|
-
'status="null"',
|
|
288
|
-
];
|
|
245
|
+
// Test case 3: Mixed usage
|
|
246
|
+
const mixedSpecs = ['name=test', 'description=', 'notes=""'];
|
|
289
247
|
const mixedMap = (0, propertySpecifications_1.parsePropertySpecificationsIntoMap)(mixedSpecs);
|
|
290
248
|
expect(mixedMap.get('name')).toBe('test');
|
|
291
249
|
expect(mixedMap.get('description')).toBeUndefined(); // Unset
|
|
292
250
|
expect(mixedMap.get('notes')).toBe(''); // Empty string
|
|
293
|
-
expect(mixedMap.
|
|
294
|
-
expect(mixedMap.get('status')).toBe('"null"'); // String "null"
|
|
295
|
-
expect(mixedMap.size).toBe(5);
|
|
251
|
+
expect(mixedMap.size).toBe(3);
|
|
296
252
|
});
|
|
297
253
|
});
|
|
298
254
|
describe('edge cases and limitations', () => {
|
|
@@ -362,20 +318,4 @@ describe('edge cases and limitations', () => {
|
|
|
362
318
|
value: 'value',
|
|
363
319
|
});
|
|
364
320
|
});
|
|
365
|
-
it('should document all special value types', () => {
|
|
366
|
-
// This test documents all the special value types and their parsing behavior
|
|
367
|
-
const specs = [
|
|
368
|
-
{ input: 'a=value', expected: 'value', description: 'regular string' },
|
|
369
|
-
{ input: 'b=', expected: undefined, description: 'undefined (unset)' },
|
|
370
|
-
{ input: 'c=""', expected: '', description: 'empty string' },
|
|
371
|
-
{ input: 'd=null', expected: null, description: 'null value' },
|
|
372
|
-
{ input: 'e="null"', expected: '"null"', description: 'string "null"' },
|
|
373
|
-
{ input: 'f=123', expected: '123', description: 'numeric string' },
|
|
374
|
-
{ input: 'g=true', expected: 'true', description: 'boolean string' },
|
|
375
|
-
];
|
|
376
|
-
for (const { input, expected } of specs) {
|
|
377
|
-
const result = (0, propertySpecifications_1.parsePropertySpecification)(input);
|
|
378
|
-
expect(result.value).toBe(expected);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
321
|
});
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.6.0-alpha-
|
|
2
|
+
"version": "3.6.0-alpha-4",
|
|
3
3
|
"commands": {
|
|
4
4
|
"app:create": {
|
|
5
5
|
"id": "app:create",
|
|
@@ -604,7 +604,8 @@
|
|
|
604
604
|
"dev",
|
|
605
605
|
"development",
|
|
606
606
|
"prod",
|
|
607
|
-
"production"
|
|
607
|
+
"production",
|
|
608
|
+
"local"
|
|
608
609
|
]
|
|
609
610
|
},
|
|
610
611
|
"apiUserToken": {
|
|
@@ -905,22 +906,6 @@
|
|
|
905
906
|
"required": false,
|
|
906
907
|
"multiple": false
|
|
907
908
|
},
|
|
908
|
-
"skipHydrateRelationships": {
|
|
909
|
-
"name": "skipHydrateRelationships",
|
|
910
|
-
"type": "boolean",
|
|
911
|
-
"char": "s",
|
|
912
|
-
"description": "Skip hydrating relationships when fetching the entity",
|
|
913
|
-
"required": false,
|
|
914
|
-
"allowNo": false
|
|
915
|
-
},
|
|
916
|
-
"relations": {
|
|
917
|
-
"name": "relations",
|
|
918
|
-
"type": "option",
|
|
919
|
-
"char": "r",
|
|
920
|
-
"description": "Comma-separated list of relations to include in the output",
|
|
921
|
-
"required": false,
|
|
922
|
-
"multiple": false
|
|
923
|
-
},
|
|
924
909
|
"propertySlugs": {
|
|
925
910
|
"name": "propertySlugs",
|
|
926
911
|
"type": "option",
|
|
@@ -937,6 +922,22 @@
|
|
|
937
922
|
"required": false,
|
|
938
923
|
"allowNo": false
|
|
939
924
|
},
|
|
925
|
+
"skip": {
|
|
926
|
+
"name": "skip",
|
|
927
|
+
"type": "option",
|
|
928
|
+
"char": "s",
|
|
929
|
+
"description": "Number of entities to skip (used with paginated fetch)",
|
|
930
|
+
"required": false,
|
|
931
|
+
"multiple": false
|
|
932
|
+
},
|
|
933
|
+
"skipHydrateRelationships": {
|
|
934
|
+
"name": "skipHydrateRelationships",
|
|
935
|
+
"type": "boolean",
|
|
936
|
+
"char": "S",
|
|
937
|
+
"description": "Skip hydrating relationships when fetching the entity",
|
|
938
|
+
"required": false,
|
|
939
|
+
"allowNo": false
|
|
940
|
+
},
|
|
940
941
|
"take": {
|
|
941
942
|
"name": "take",
|
|
942
943
|
"type": "option",
|
|
@@ -944,6 +945,14 @@
|
|
|
944
945
|
"description": "Number of entities to fetch in a single request (default is 100)",
|
|
945
946
|
"required": false,
|
|
946
947
|
"multiple": false
|
|
948
|
+
},
|
|
949
|
+
"relations": {
|
|
950
|
+
"name": "relations",
|
|
951
|
+
"type": "option",
|
|
952
|
+
"char": "r",
|
|
953
|
+
"description": "Comma-separated list of relations to include in the output",
|
|
954
|
+
"required": false,
|
|
955
|
+
"multiple": false
|
|
947
956
|
}
|
|
948
957
|
},
|
|
949
958
|
"args": {
|
|
@@ -964,7 +973,7 @@
|
|
|
964
973
|
"aliases": [],
|
|
965
974
|
"examples": [
|
|
966
975
|
"contrail entities update color 123 -- name=obsidian hex=000000",
|
|
967
|
-
"contrail entities update color 123 -- description= notes='\"\"'
|
|
976
|
+
"contrail entities update color 123 -- description= notes='\"\"' # Unset description, set notes to empty string"
|
|
968
977
|
],
|
|
969
978
|
"flags": {
|
|
970
979
|
"logLevel": {
|
|
@@ -1017,6 +1026,14 @@
|
|
|
1017
1026
|
"description": "File containing properties to update the entity (optional)",
|
|
1018
1027
|
"required": false,
|
|
1019
1028
|
"multiple": false
|
|
1029
|
+
},
|
|
1030
|
+
"force": {
|
|
1031
|
+
"name": "force",
|
|
1032
|
+
"type": "boolean",
|
|
1033
|
+
"char": "F",
|
|
1034
|
+
"description": "Force update without validation. Internal use only",
|
|
1035
|
+
"required": false,
|
|
1036
|
+
"allowNo": false
|
|
1020
1037
|
}
|
|
1021
1038
|
},
|
|
1022
1039
|
"args": {
|
|
@@ -1028,7 +1045,7 @@
|
|
|
1028
1045
|
"id": {
|
|
1029
1046
|
"name": "id",
|
|
1030
1047
|
"description": "The unique identifier of the entity",
|
|
1031
|
-
"required":
|
|
1048
|
+
"required": false
|
|
1032
1049
|
}
|
|
1033
1050
|
}
|
|
1034
1051
|
},
|