@contrail/cli 2.3.4 → 2.3.5

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/2.3.4 darwin-arm64 node-v20.12.1
20
+ @contrail/cli/2.3.5 darwin-arm64 node-v22.3.0
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -876,11 +876,14 @@ Configure a project from a yml file.
876
876
 
877
877
  ```
878
878
  USAGE
879
- $ contrail projects load TEMPLATEFILELOCATION
879
+ $ contrail projects load TEMPLATEFILELOCATION [--dumpTransformedFile]
880
880
 
881
881
  ARGUMENTS
882
882
  TEMPLATEFILELOCATION The location to the template file
883
883
 
884
+ FLAGS
885
+ --dumpTransformedFile If set to true, stores a transformed version of the template file
886
+
884
887
  DESCRIPTION
885
888
  Configure a project from a yml file.
886
889
 
@@ -5,6 +5,9 @@ export default class LoadProject extends BaseCommand {
5
5
  static args: {
6
6
  templateFileLocation: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
7
7
  };
8
+ static flags: {
9
+ dumpTransformedFile: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
8
11
  run(): Promise<void>;
9
12
  }
10
13
  interface CheckerFunction {
@@ -9,6 +9,8 @@ const login_1 = require("../../auth/login");
9
9
  const basecommand_1 = require("../../basecommand");
10
10
  const helpers_1 = require("../../helpers/projects/helpers");
11
11
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
12
+ const pLimit = require('p-limit');
13
+ const limit = pLimit(30);
12
14
  class LoadProject extends basecommand_1.BaseCommand {
13
15
  static description = 'Configure a project from a yml file.';
14
16
  static examples = [
@@ -21,18 +23,26 @@ class LoadProject extends basecommand_1.BaseCommand {
21
23
  required: true,
22
24
  }),
23
25
  };
26
+ static flags = {
27
+ ...basecommand_1.BaseCommand.flags,
28
+ dumpTransformedFile: core_1.Flags.boolean({
29
+ description: 'If set to true, stores a transformed version of the template file',
30
+ default: false,
31
+ }),
32
+ };
24
33
  async run() {
25
- const { args } = await this.parse(LoadProject);
34
+ const { args, flags } = await this.parse(LoadProject);
26
35
  try {
27
36
  await (0, login_1.authUser)();
28
37
  this.log('%s Parsing Template.', chalk_1.default.blue.bold('START'));
29
38
  this.log('%s Uploading', chalk_1.default.yellow.bold('PROJECT'));
30
39
  const templateFileLocation = args.templateFileLocation;
31
40
  this.log('Using Template File Location: ', args.templateFileLocation);
32
- await uploadProject(templateFileLocation);
41
+ await uploadProject(templateFileLocation, flags);
33
42
  this.log('%s Uploaded', chalk_1.default.yellow.bold('PROJECT'));
34
43
  }
35
44
  catch (e) {
45
+ console.error(e);
36
46
  this.log(`%s Project Upload Failed. ${e}`, chalk_1.default.red.bold('FAIL'));
37
47
  return;
38
48
  }
@@ -40,11 +50,16 @@ class LoadProject extends basecommand_1.BaseCommand {
40
50
  }
41
51
  }
42
52
  exports.default = LoadProject;
43
- async function uploadProject(templateFileLocation) {
53
+ async function uploadProject(templateFileLocation, flags) {
44
54
  const projectConfigurationFromFile = (0, helpers_1.getProjectFile)(templateFileLocation);
45
55
  const projectConfiguration = getProjectConfiguration(projectConfigurationFromFile);
46
56
  logger_1.default.info('Checking for object and type references');
47
57
  await transformProject(projectConfiguration);
58
+ if (flags.dumpTransformedFile) {
59
+ logger_1.default.info('Dumping transformed project file');
60
+ const fs = require('fs');
61
+ fs.writeFileSync(templateFileLocation + '.transformed', JSON.stringify(projectConfiguration, null, 2));
62
+ }
48
63
  logger_1.default.info('Configuring Project.');
49
64
  await sdk_1.Request.request('/project-configurator', {
50
65
  method: 'POST',
@@ -155,43 +170,47 @@ async function transformAssortmentObjectReferences(_, assortments) {
155
170
  relations: ['typeProperties'],
156
171
  });
157
172
  const assortmentProperties = assortmentType.typeProperties;
173
+ const promises = [];
158
174
  for (const assortment of assortments ?? []) {
159
- const attributes = JSON.parse(JSON.stringify(assortment?.assortmentAtts ?? {}));
160
- if (!Object.keys(attributes)?.length) {
161
- continue;
162
- }
163
- for (const [attributeKey, attributeValue] of Object.entries(attributes)) {
164
- const property = assortmentProperties.find((x) => x.slug === attributeKey);
165
- // if not a reference
166
- if (!property || !property.referencedTypePath) {
167
- continue;
175
+ promises.push(limit(async () => {
176
+ const attributes = JSON.parse(JSON.stringify(assortment?.assortmentAtts ?? {}));
177
+ if (!Object.keys(attributes)?.length) {
178
+ return;
168
179
  }
169
- if (property.propertyType === types_1.PropertyType.TypeReference) {
170
- logger_1.default.debug(`found type reference!${attributeKey}${attributeValue}`);
171
- // special case: handle type references
172
- const type = (await new sdk_1.Types().getType({ path: attributeValue }))?.id;
173
- logger_1.default.debug(`found type ${type}`);
174
- logger_1.default.info(`replacing ${attributeKey}:${attributeValue} with ${attributeKey}Id:${type}`);
175
- assortment.assortmentAtts[`${attributeKey}Id`] = type;
176
- }
177
- else if (property.propertyType === types_1.PropertyType.ObjectReference) {
178
- logger_1.default.debug(`found object reference!${attributeKey}${attributeValue}`);
179
- let allEntities = [];
180
- if (property.referencedTypePath.startsWith('custom-entity')) {
181
- allEntities = await new sdk_1.Entities().get({
182
- entityName: 'custom-entity',
183
- criteria: { typePath: property.referencedTypePath },
184
- });
180
+ for (const [attributeKey, attributeValue] of Object.entries(attributes)) {
181
+ const property = assortmentProperties.find((x) => x.slug === attributeKey);
182
+ // if not a reference
183
+ if (!property || !property.referencedTypePath) {
184
+ continue;
185
+ }
186
+ if (property.propertyType === types_1.PropertyType.TypeReference) {
187
+ logger_1.default.debug(`found type reference!${attributeKey}${attributeValue}`);
188
+ // special case: handle type references
189
+ const type = (await new sdk_1.Types().getType({ path: attributeValue }))?.id;
190
+ logger_1.default.debug(`found type ${type}`);
191
+ logger_1.default.info(`replacing ${attributeKey}:${attributeValue} with ${attributeKey}Id:${type}`);
192
+ assortment.assortmentAtts[`${attributeKey}Id`] = type;
185
193
  }
186
- else {
187
- allEntities = await new sdk_1.Entities().get({ entityName: property.referencedTypePath });
194
+ else if (property.propertyType === types_1.PropertyType.ObjectReference) {
195
+ logger_1.default.debug(`found object reference!${attributeKey}${attributeValue}`);
196
+ let allEntities = [];
197
+ if (property.referencedTypePath.startsWith('custom-entity')) {
198
+ allEntities = await new sdk_1.Entities().get({
199
+ entityName: 'custom-entity',
200
+ criteria: { typePath: property.referencedTypePath },
201
+ });
202
+ }
203
+ else {
204
+ allEntities = await new sdk_1.Entities().get({ entityName: property.referencedTypePath });
205
+ }
206
+ // special case: handle type references
207
+ const foundAttribute = allEntities.find((x) => x.name === attributeValue)?.id ?? attributeKey;
208
+ logger_1.default.debug(`found attribute ${foundAttribute}`);
209
+ logger_1.default.info(`replacing ${attributeKey}:${attributeValue} with ${attributeKey}Id:${foundAttribute}`);
210
+ assortment.assortmentAtts[`${attributeKey}Id`] = foundAttribute;
188
211
  }
189
- // special case: handle type references
190
- const foundAttribute = allEntities.find((x) => x.name === attributeValue)?.id ?? attributeKey;
191
- logger_1.default.debug(`found attribute ${foundAttribute}`);
192
- logger_1.default.info(`replacing ${attributeKey}:${attributeValue} with ${attributeKey}Id:${foundAttribute}`);
193
- assortment.assortmentAtts[`${attributeKey}Id`] = foundAttribute;
194
212
  }
195
- }
213
+ }));
214
+ await Promise.all(promises);
196
215
  }
197
216
  }
@@ -55,6 +55,7 @@ async function mergeOptionSets(fileLocation) {
55
55
  const currentOptionSets = currentOptionSetsFull.map((os) => Object.assign({}, {
56
56
  name: os.name,
57
57
  isArchived: os.isArchived,
58
+ slug: os.slug,
58
59
  optionSet: os.optionSet,
59
60
  }));
60
61
  const currentOptionSetsByName = {};
@@ -101,9 +102,10 @@ function mergeOptionSet(existingOptionSet, uploadedOptionSet) {
101
102
  for (const keyVal of existingOptionSet.optionSet) {
102
103
  const value = keyVal.value;
103
104
  const display = keyVal.display;
105
+ const disabled = keyVal?.disabled;
104
106
  const found = merged.optionSet.find((keyVal) => keyVal.value === value);
105
107
  if (!found) {
106
- merged.optionSet.push({ value, display });
108
+ merged.optionSet.push({ value, display, disabled });
107
109
  }
108
110
  }
109
111
  }
@@ -18,18 +18,20 @@ export declare function ParseOptionSetsToFolderStructure(args: any): Promise<voi
18
18
  */
19
19
  export declare function parseLabelsInColumn(uploadedOptionData: any): any[];
20
20
  /**
21
- * Expects a CSV file with an ignored header file and 3 columns:
21
+ * Expects a CSV file with an ignored header file and 4 columns:
22
22
  * The first column contains the option set name (e.g. Gender)
23
23
  * The second column contains the option keys (e.g. ge_men)
24
- * The second column contains the option display label (e.g. Men)
24
+ * The third column contains the option display label (e.g. Men)
25
+ * The fourth column contains the disabled value (e.g. true / false)
25
26
  *
26
27
  * Sample file
27
- * ignored header 1, ignored header 2, ignored header 3
28
- * Gender, ge_men, Men
29
- * Gender, ge_women, Women
30
- * Brand, b_prada, Prada
31
- * Brand, b_coach, Coach
28
+ * ignored header 1, ignored header 2, ignored header 3, ignored header 4
29
+ * Gender, ge_men, Men, true
30
+ * Gender, ge_women, Women, false
31
+ * Brand, b_prada, Prada, true
32
+ * Brand, b_coach, Coach, true
32
33
  * @param uploadedOptionData
33
34
  * @returns
34
35
  */
35
36
  export declare function parseNameAndLabelAndKey(uploadedOptionData: any[]): any[];
37
+ export declare function parseDisabledValue(stringValue: string): boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseNameAndLabelAndKey = exports.parseLabelsInColumn = exports.ParseOptionSetsToFolderStructure = void 0;
3
+ exports.parseDisabledValue = exports.parseNameAndLabelAndKey = exports.parseLabelsInColumn = exports.ParseOptionSetsToFolderStructure = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
6
  const fs_1 = tslib_1.__importDefault(require("fs"));
@@ -154,7 +154,7 @@ function parseLabelsInColumn(uploadedOptionData) {
154
154
  const uniqueOptionSetValues = new Set();
155
155
  for (const data of uploadedOptionData) {
156
156
  const label = data[optionSetName];
157
- uniqueOptionSetValues.add(label);
157
+ uniqueOptionSetValues.add(label.toString()); // Parse the value to string
158
158
  }
159
159
  const optionSet = [];
160
160
  if (uniqueOptionSetValues) {
@@ -180,17 +180,18 @@ function parseLabelsInColumn(uploadedOptionData) {
180
180
  }
181
181
  exports.parseLabelsInColumn = parseLabelsInColumn;
182
182
  /**
183
- * Expects a CSV file with an ignored header file and 3 columns:
183
+ * Expects a CSV file with an ignored header file and 4 columns:
184
184
  * The first column contains the option set name (e.g. Gender)
185
185
  * The second column contains the option keys (e.g. ge_men)
186
- * The second column contains the option display label (e.g. Men)
186
+ * The third column contains the option display label (e.g. Men)
187
+ * The fourth column contains the disabled value (e.g. true / false)
187
188
  *
188
189
  * Sample file
189
- * ignored header 1, ignored header 2, ignored header 3
190
- * Gender, ge_men, Men
191
- * Gender, ge_women, Women
192
- * Brand, b_prada, Prada
193
- * Brand, b_coach, Coach
190
+ * ignored header 1, ignored header 2, ignored header 3, ignored header 4
191
+ * Gender, ge_men, Men, true
192
+ * Gender, ge_women, Women, false
193
+ * Brand, b_prada, Prada, true
194
+ * Brand, b_coach, Coach, true
194
195
  * @param uploadedOptionData
195
196
  * @returns
196
197
  */
@@ -198,7 +199,7 @@ function parseNameAndLabelAndKey(uploadedOptionData) {
198
199
  const optionSets = {};
199
200
  for (const csvRow of uploadedOptionData) {
200
201
  const row = Object.values(csvRow);
201
- if (row.length !== 3 || row.some((x) => !x)) {
202
+ if (!(row.length === 4 || row.length === 3) || row.some((x) => !x)) {
202
203
  console.log('skipping bad row', JSON.stringify(row, null, 2));
203
204
  continue;
204
205
  }
@@ -208,15 +209,23 @@ function parseNameAndLabelAndKey(uploadedOptionData) {
208
209
  }
209
210
  const key = row[1];
210
211
  const label = row[2];
211
- optionSets[optionSetName][key] = label;
212
+ let disabled = false;
213
+ if (row.length === 4) {
214
+ // as bad row is skipped above, testString is always a string
215
+ const testString = row[3];
216
+ disabled = parseDisabledValue(testString);
217
+ }
218
+ optionSets[optionSetName][key] = { label, disabled };
212
219
  }
213
220
  const result = [];
214
221
  for (const optionSetName of Object.keys(optionSets)) {
215
222
  const optionSet = [];
216
- for (const [key, value] of Object.entries(optionSets[optionSetName])) {
223
+ for (const [key, obj] of Object.entries(optionSets[optionSetName])) {
224
+ const optionObj = obj; // Add type annotation
217
225
  optionSet.push({
218
- value: key,
219
- display: value,
226
+ value: key.toString(),
227
+ display: optionObj?.label,
228
+ disabled: optionObj?.disabled, // Use the type annotated variable
220
229
  });
221
230
  }
222
231
  const reducedOptionSet = {
@@ -230,3 +239,13 @@ function parseNameAndLabelAndKey(uploadedOptionData) {
230
239
  return result;
231
240
  }
232
241
  exports.parseNameAndLabelAndKey = parseNameAndLabelAndKey;
242
+ function parseDisabledValue(stringValue) {
243
+ const testString = /^true$/i.test(stringValue?.trim());
244
+ const isDisabledFalse = testString === false;
245
+ const isDisabledTrue = testString === true;
246
+ if (!isDisabledFalse && !isDisabledTrue) {
247
+ console.warn(`Invalid value (${stringValue}) for disabled attribute. Must be either "true" or "false"`);
248
+ }
249
+ return isDisabledTrue;
250
+ }
251
+ exports.parseDisabledValue = parseDisabledValue;
@@ -8,3 +8,4 @@ export default class UploadOptionSets extends BaseCommand {
8
8
  run(): Promise<void>;
9
9
  }
10
10
  export declare function uploadOptionSets(dir: string): Promise<void>;
11
+ export declare function parseOptionSetValuesToString(optionSet: any): any;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadOptionSets = void 0;
3
+ exports.parseOptionSetValuesToString = exports.uploadOptionSets = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const sdk_1 = require("@contrail/sdk");
6
6
  const fs_1 = tslib_1.__importDefault(require("fs"));
@@ -11,6 +11,7 @@ const core_1 = require("@oclif/core");
11
11
  const basecommand_1 = require("../../basecommand");
12
12
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
13
13
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
14
+ const lodash_1 = require("lodash");
14
15
  class UploadOptionSets extends basecommand_1.BaseCommand {
15
16
  static description = 'Upload OptionSets for your Org. OptionSets are loaded from .yml files in the option-sets directory.';
16
17
  static examples = [
@@ -34,7 +35,7 @@ exports.default = UploadOptionSets;
34
35
  async function uploadOptionSets(dir) {
35
36
  try {
36
37
  const userConfig = await (0, login_1.authUser)();
37
- logger_1.default.info(`${chalk_1.default.blue.bold('START')} Getting all types for orgId: ${userConfig.orgId}`);
38
+ logger_1.default.info(`${chalk_1.default.blue.bold('START')} Getting all types for org ${userConfig.orgSlug}`);
38
39
  logger_1.default.info(`${chalk_1.default.yellow.bold('OPTION SETS')} Uploading`);
39
40
  await uploadOS(dir);
40
41
  logger_1.default.info(`${chalk_1.default.yellow.bold('OPTION SETS')} Uploaded`);
@@ -60,6 +61,17 @@ function loadOptionSets(fromDir) {
60
61
  throw new Error(`ERROR PARSING YAML: ${e}`);
61
62
  }
62
63
  }
64
+ function parseOptionSetValuesToString(optionSet) {
65
+ return (0, lodash_1.cloneDeepWith)(optionSet, (value) => {
66
+ // If the value is an object or array, let cloneDeepWith handle it
67
+ if ((0, lodash_1.isObject)(value) || (0, lodash_1.isArray)(value)) {
68
+ return undefined;
69
+ }
70
+ // Otherwise, convert the value to string
71
+ return String(value);
72
+ });
73
+ }
74
+ exports.parseOptionSetValuesToString = parseOptionSetValuesToString;
63
75
  async function uploadOS(dir) {
64
76
  const optionSets = loadOptionSets(dir);
65
77
  const currentOptionSets = await new sdk_1.Entities().get({
@@ -83,18 +95,20 @@ async function uploadOS(dir) {
83
95
  logger_1.default.info(`No OptionSet for: ${optionSet.slug}`);
84
96
  continue;
85
97
  }
86
- logger_1.default.info(`Creating: ${JSON.stringify(optionSet, null, 2)}`);
98
+ const parsedOptionSet = parseOptionSetValuesToString(optionSet.optionSet);
99
+ logger_1.default.info(`Creating: ${JSON.stringify(parsedOptionSet, null, 2)}`);
87
100
  await new sdk_1.Entities().create({
88
101
  entityName: 'type-property-option-set',
89
102
  object: {
90
103
  name: optionSet.name,
91
104
  slug: optionSet.slug,
92
- optionSet: optionSet.optionSet,
105
+ optionSet: parsedOptionSet,
93
106
  isArchived: optionSet.isArchived,
94
107
  },
95
108
  });
96
109
  }
97
110
  else {
111
+ const parsedOptionSet = parseOptionSetValuesToString(optionSet.optionSet);
98
112
  logger_1.default.info(`Updating: ${optionSet.slug}`);
99
113
  await new sdk_1.Entities().update({
100
114
  entityName: 'type-property-option-set',
@@ -102,7 +116,7 @@ async function uploadOS(dir) {
102
116
  object: {
103
117
  name: optionSet.name,
104
118
  slug: optionSet.slug,
105
- optionSet: optionSet.optionSet,
119
+ optionSet: parsedOptionSet,
106
120
  isArchived: optionSet.isArchived,
107
121
  },
108
122
  });
@@ -112,19 +126,18 @@ async function uploadOS(dir) {
112
126
  logger_1.default.error(`Error Uploading OptionSets: ${err}`);
113
127
  }
114
128
  }
115
- for (const currentOptionSet of currentOptionSets) {
116
- try {
117
- const found = newOptionSetsBySlug[currentOptionSet.slug];
118
- if (!found) {
119
- logger_1.default.info(`Deleting: ${currentOptionSet.slug}`);
120
- await new sdk_1.Entities().delete({
121
- entityName: 'type-property-option-set',
122
- id: currentOptionSet.id,
123
- });
124
- }
125
- }
126
- catch (err) {
127
- logger_1.default.error(`Error Deleting OptionSets: ${err}`);
128
- }
129
- }
129
+ // for (const currentOptionSet of currentOptionSets) {
130
+ // try {
131
+ // const found = newOptionSetsBySlug[currentOptionSet.slug];
132
+ // if (!found) {
133
+ // logger.info(`Deleting: ${currentOptionSet.slug}`);
134
+ // await new Entities().delete({
135
+ // entityName: 'type-property-option-set',
136
+ // id: currentOptionSet.id,
137
+ // });
138
+ // }
139
+ // } catch (err) {
140
+ // logger.error(`Error Deleting OptionSets: ${err}`);
141
+ // }
142
+ // }
130
143
  }
@@ -8,6 +8,9 @@ const fs_1 = tslib_1.__importDefault(require("fs"));
8
8
  const sdk_1 = require("@contrail/sdk");
9
9
  const util_1 = require("@contrail/util");
10
10
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
11
+ const uuid_1 = require("uuid");
12
+ const pLimit = require('p-limit');
13
+ const limit = pLimit(30);
11
14
  function getProjectFile(fileName) {
12
15
  const CURRENT_DIR = process.cwd();
13
16
  try {
@@ -52,7 +55,7 @@ async function getAssortmentsAndSubFolders(parentId, assortments, subFolders, al
52
55
  workspaceId: parentId,
53
56
  },
54
57
  });
55
- const integrationAssortments = workspaceAssortments; //?.filter(a => a.assortmentType === 'INTEGRATION');
58
+ const integrationAssortments = workspaceAssortments.filter((x) => x.assortmentType !== 'BACKING'); //?.filter(a => a.assortmentType === 'INTEGRATION');
56
59
  if (integrationAssortments?.length > 0) {
57
60
  allAssortments.push(...integrationAssortments);
58
61
  for (const a of integrationAssortments) {
@@ -60,7 +63,7 @@ async function getAssortmentsAndSubFolders(parentId, assortments, subFolders, al
60
63
  deleteNonUserKeys(integrationAssortmentsCopy);
61
64
  const assortmentType = a.assortmentType === 'INTEGRATION' ? 'ASSORTMENT' : a.assortmentType;
62
65
  const converted = {
63
- identifier: a.identifier,
66
+ identifier: a.identifier ?? `${a.name}_${(0, uuid_1.v4)()}.assortment`,
64
67
  name: a.name,
65
68
  type: assortmentType,
66
69
  sourceAssortmentIdentifier: [],
@@ -82,20 +85,18 @@ async function getAssortmentsAndSubFolders(parentId, assortments, subFolders, al
82
85
  relations: ['entity'],
83
86
  });
84
87
  const workspaceFolders = workspaceFolderLinks?.map((wfl) => wfl.entity);
85
- if (workspaceFolders?.length > 0) {
86
- for (const folder of workspaceFolders) {
87
- const converted = {
88
- identifier: folder.identifier,
89
- name: folder.name,
90
- principals: [],
91
- assortments: [],
92
- subFolders: [],
93
- };
94
- converted.principals = await getFolderPrincipals(folder);
95
- await getAssortmentsAndSubFolders(folder.id, converted.assortments, converted.subFolders, allAssortments);
96
- subFolders.push(converted);
97
- }
98
- }
88
+ await Promise.all((workspaceFolders ?? [])?.map((folder) => limit(async () => {
89
+ const converted = {
90
+ identifier: folder.identifier ?? `${folder.name}_${(0, uuid_1.v4)()}.folder`,
91
+ name: folder.name,
92
+ principals: [],
93
+ assortments: [],
94
+ subFolders: [],
95
+ };
96
+ converted.principals = await getFolderPrincipals(folder);
97
+ await getAssortmentsAndSubFolders(folder.id, converted.assortments, converted.subFolders, allAssortments);
98
+ subFolders.push(converted);
99
+ })));
99
100
  }
100
101
  function convertRelationshipsInFolders(folder, allAssortments) {
101
102
  if (!(allAssortments.length > 0)) {
@@ -159,6 +160,10 @@ function deleteNonUserKeys(assortment) {
159
160
  delete assortment.updatedById;
160
161
  delete assortment.updatedOn;
161
162
  delete assortment.workspaceId;
163
+ delete assortment.itemTypeId;
164
+ if (assortment.itemType && typeof assortment.itemType === 'object') {
165
+ assortment.itemType = assortment.itemType?.typePath;
166
+ }
162
167
  }
163
168
  function saveData(data, name) {
164
169
  const yamlStr = js_yaml_1.default.dump(data);
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const parseOptionSetsLocalFiles_1 = require("../../commands/types/parseOptionSetsLocalFiles");
4
+ describe('parseNameAndLabelAndKey', () => {
5
+ it('should parse Name, Value, and Display, Disabled columns correctly if they are all present', () => {
6
+ const input = [
7
+ {
8
+ Name: 'Category',
9
+ Value: 'bottoms',
10
+ Display: 'Bottoms',
11
+ Disabled: 'false',
12
+ },
13
+ {
14
+ Name: 'Category',
15
+ Value: 'dresses',
16
+ Display: 'Dresses',
17
+ Disabled: 'true',
18
+ },
19
+ {
20
+ Name: 'Category',
21
+ Value: 'jacket',
22
+ Display: 'Jacket',
23
+ Disabled: 'true',
24
+ },
25
+ { Name: 'Fit', Value: 'active', Display: 'Active', Disabled: 'TRUE' },
26
+ ];
27
+ const expectedOutput = [
28
+ {
29
+ name: 'Category',
30
+ slug: 'category',
31
+ isArchived: false,
32
+ optionSet: [
33
+ {
34
+ value: 'bottoms',
35
+ display: 'Bottoms',
36
+ disabled: false,
37
+ },
38
+ {
39
+ value: 'dresses',
40
+ display: 'Dresses',
41
+ disabled: true,
42
+ },
43
+ {
44
+ value: 'jacket',
45
+ display: 'Jacket',
46
+ disabled: true,
47
+ },
48
+ ],
49
+ },
50
+ {
51
+ name: 'Fit',
52
+ slug: 'fit',
53
+ isArchived: false,
54
+ optionSet: [
55
+ {
56
+ value: 'active',
57
+ display: 'Active',
58
+ disabled: true,
59
+ },
60
+ ],
61
+ },
62
+ ];
63
+ const result = (0, parseOptionSetsLocalFiles_1.parseNameAndLabelAndKey)(input);
64
+ expect(result).toEqual(expectedOutput);
65
+ });
66
+ it('should skip invalid rows or rows with incorrect number of columns', () => {
67
+ const input = [
68
+ {
69
+ Name: 'Category',
70
+ Value: 'dresses',
71
+ Display: 'Dresses',
72
+ },
73
+ {
74
+ Name: 'Category',
75
+ Value: 'bottoms',
76
+ Display: 'Bottoms',
77
+ Disabled: '', // this is invalid row which should be skipped
78
+ },
79
+ {
80
+ Name: 'Category',
81
+ Value: 'jacket',
82
+ },
83
+ { Name: 'Fit', Value: 'active', Display: 'Active', Disabled: 'true' },
84
+ ];
85
+ const expectedOutput = [
86
+ {
87
+ name: 'Category',
88
+ slug: 'category',
89
+ isArchived: false,
90
+ optionSet: [
91
+ {
92
+ value: 'dresses',
93
+ display: 'Dresses',
94
+ disabled: false,
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ name: 'Fit',
100
+ slug: 'fit',
101
+ isArchived: false,
102
+ optionSet: [
103
+ {
104
+ value: 'active',
105
+ display: 'Active',
106
+ disabled: true,
107
+ },
108
+ ],
109
+ },
110
+ ];
111
+ const result = (0, parseOptionSetsLocalFiles_1.parseNameAndLabelAndKey)(input);
112
+ expect(result).toEqual(expectedOutput);
113
+ });
114
+ it('should set disabled value to default value of false for invalid row values', () => {
115
+ const input = [
116
+ {
117
+ Name: 'Category',
118
+ Value: 'bottoms',
119
+ Display: 'Bottoms',
120
+ Disabled: 'false',
121
+ },
122
+ {
123
+ Name: 'Category',
124
+ Value: 'dresses',
125
+ Display: 'Dresses',
126
+ Disabled: 'test',
127
+ },
128
+ {
129
+ Name: 'Category',
130
+ Value: 'jacket',
131
+ Display: 'Jacket',
132
+ Disabled: 'abc',
133
+ },
134
+ { Name: 'Fit', Value: 'active', Display: 'Active', Disabled: 'true' },
135
+ ];
136
+ const expectedOutput = [
137
+ {
138
+ name: 'Category',
139
+ slug: 'category',
140
+ isArchived: false,
141
+ optionSet: [
142
+ {
143
+ value: 'bottoms',
144
+ display: 'Bottoms',
145
+ disabled: false,
146
+ },
147
+ {
148
+ value: 'dresses',
149
+ display: 'Dresses',
150
+ disabled: false,
151
+ },
152
+ {
153
+ value: 'jacket',
154
+ display: 'Jacket',
155
+ disabled: false,
156
+ },
157
+ ],
158
+ },
159
+ {
160
+ name: 'Fit',
161
+ slug: 'fit',
162
+ isArchived: false,
163
+ optionSet: [
164
+ {
165
+ value: 'active',
166
+ display: 'Active',
167
+ disabled: true,
168
+ },
169
+ ],
170
+ },
171
+ ];
172
+ const result = (0, parseOptionSetsLocalFiles_1.parseNameAndLabelAndKey)(input);
173
+ expect(result).toEqual(expectedOutput);
174
+ });
175
+ it('should handle missing disabled field correctly', () => {
176
+ const input = [
177
+ {
178
+ Name: 'Category',
179
+ Value: 'dresses',
180
+ Display: 'Dresses',
181
+ },
182
+ { Name: 'Fit', Value: 'active', Display: 'Active' },
183
+ ];
184
+ const expectedOutput = [
185
+ {
186
+ name: 'Category',
187
+ slug: 'category',
188
+ isArchived: false,
189
+ optionSet: [
190
+ {
191
+ value: 'dresses',
192
+ display: 'Dresses',
193
+ disabled: false,
194
+ },
195
+ ],
196
+ },
197
+ {
198
+ name: 'Fit',
199
+ slug: 'fit',
200
+ isArchived: false,
201
+ optionSet: [
202
+ {
203
+ value: 'active',
204
+ display: 'Active',
205
+ disabled: false,
206
+ },
207
+ ],
208
+ },
209
+ ];
210
+ const result = (0, parseOptionSetsLocalFiles_1.parseNameAndLabelAndKey)(input);
211
+ expect(result).toEqual(expectedOutput);
212
+ });
213
+ it('should convert number value to string', () => {
214
+ const input = [
215
+ {
216
+ Name: 'Category',
217
+ Value: 123,
218
+ Display: 'Category',
219
+ Disabled: 'false',
220
+ },
221
+ ];
222
+ const expectedOutput = [
223
+ {
224
+ name: 'Category',
225
+ slug: 'category',
226
+ isArchived: false,
227
+ optionSet: [
228
+ {
229
+ value: '123',
230
+ display: 'Category',
231
+ disabled: false,
232
+ },
233
+ ],
234
+ },
235
+ ];
236
+ const result = (0, parseOptionSetsLocalFiles_1.parseNameAndLabelAndKey)(input);
237
+ expect(result).toEqual(expectedOutput);
238
+ });
239
+ describe('parseDisabledValue', () => {
240
+ it('should return true for valid "true" string', () => {
241
+ const input = 'true';
242
+ const result = (0, parseOptionSetsLocalFiles_1.parseDisabledValue)(input);
243
+ expect(result).toBe(true);
244
+ });
245
+ it('should return true for valid case insensitive "true" string', () => {
246
+ const input = 'TRUE';
247
+ const result = (0, parseOptionSetsLocalFiles_1.parseDisabledValue)(input);
248
+ expect(result).toBe(true);
249
+ });
250
+ it('should return false for valid "false" string', () => {
251
+ const input = 'false';
252
+ const result = (0, parseOptionSetsLocalFiles_1.parseDisabledValue)(input);
253
+ expect(result).toBe(false);
254
+ });
255
+ it('should return true for valid case insensitive "false" string', () => {
256
+ const input = 'FALSE';
257
+ const result = (0, parseOptionSetsLocalFiles_1.parseDisabledValue)(input);
258
+ expect(result).toBe(false);
259
+ });
260
+ it('should return false for invalid string', () => {
261
+ const input = 'invalid';
262
+ const result = (0, parseOptionSetsLocalFiles_1.parseDisabledValue)(input);
263
+ expect(result).toBe(false);
264
+ });
265
+ });
266
+ });
267
+ describe('parseLabelsInColumn', () => {
268
+ it('should parse option sets with number values as strings', () => {
269
+ const input = [{ Gender: 1 }, { Gender: 2 }, { Gender: 3 }];
270
+ const expectedOutput = [
271
+ {
272
+ name: 'Gender',
273
+ slug: 'gender',
274
+ isArchived: false,
275
+ optionSet: [
276
+ { value: '1', display: '1' },
277
+ { value: '2', display: '2' },
278
+ { value: '3', display: '3' },
279
+ ],
280
+ },
281
+ ];
282
+ const result = (0, parseOptionSetsLocalFiles_1.parseLabelsInColumn)(input);
283
+ expect(result).toEqual(expectedOutput);
284
+ });
285
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const uploadOptionSets_1 = require("../../commands/types/uploadOptionSets");
4
+ describe('parseOptionSetValuesToString', () => {
5
+ it('should convert all option set values to strings', () => {
6
+ const input = {
7
+ value1: 123,
8
+ value2: true,
9
+ value3: { nested: 'object' },
10
+ };
11
+ const expectedOutput = {
12
+ value1: '123',
13
+ value2: 'true',
14
+ value3: { nested: 'object' },
15
+ };
16
+ const result = (0, uploadOptionSets_1.parseOptionSetValuesToString)(input);
17
+ expect(result).toEqual(expectedOutput);
18
+ });
19
+ });
@@ -62,6 +62,8 @@ class WorkflowTemplateDefinitionDeployer {
62
62
  identifier: definition.identifier,
63
63
  builtIn: definition.builtIn,
64
64
  messageGroupId: definition.messageGroupId,
65
+ messageGroup: definition.messageGroup,
66
+ dynamicMessageGroupId: definition.dynamicMessageGroupId,
65
67
  parallelWorkerCount: definition.parallelWorkerCount,
66
68
  triggerKey: definition.triggerKey,
67
69
  workflowDefinition: definition.workflowDefinition,
@@ -115,6 +117,8 @@ class WorkflowTemplateDefinitionDeployer {
115
117
  public: workflowDeclaration.public,
116
118
  isActive: workflowDeclaration.isActive,
117
119
  messageGroupId: workflowDeclaration.messageGroupId,
120
+ messageGroup: workflowDeclaration.messageGroup,
121
+ dynamicMessageGroupId: workflowDeclaration.dynamicMessageGroupId,
118
122
  parallelWorkerCount: workflowDeclaration.parallelWorkerCount,
119
123
  };
120
124
  const workflowTemplate = await (0, event_workflow_template_definition_1.createWorkflowTemplateDefinition)(newObject);
@@ -44,6 +44,8 @@ class LocalWorkflowTemplateDeployer {
44
44
  isActive: workflowTemplate.isActive,
45
45
  supportingAppId: workflowTemplate.supportingAppId,
46
46
  messageGroupId: workflowTemplate.messageGroupId,
47
+ messageGroup: workflowTemplate.messageGroup,
48
+ dynamicMessageGroupId: workflowTemplate.dynamicMessageGroupId,
47
49
  parallelWorkerCount: workflowTemplate.parallelWorkerCount,
48
50
  };
49
51
  const newWorkflowTemplate = await (0, event_workflow_templates_1.createWorkflowTemplate)(newObject);
@@ -159,6 +161,8 @@ class LocalWorkflowTemplateDeployer {
159
161
  builtIn: workflowTemplate.builtIn,
160
162
  workflowDefinition: workflowTemplate.workflowDefinition,
161
163
  messageGroupId: workflowTemplate.messageGroupId ?? existingWorkflowTemplate.messageGroupId,
164
+ messageGroup: workflowTemplate.messageGroup ?? existingWorkflowTemplate.messageGroup,
165
+ dynamicMessageGroupId: workflowTemplate.dynamicMessageGroupId ?? existingWorkflowTemplate.dynamicMessageGroupId,
162
166
  parallelWorkerCount: workflowTemplate.parallelWorkerCount ?? existingWorkflowTemplate.parallelWorkerCount,
163
167
  });
164
168
  const updatedWorkflowTemplate = await (0, event_workflow_templates_1.updateWorkflowTemplate)({
@@ -73,6 +73,8 @@ export interface WorkflowDefinition {
73
73
  supportingAppId?: string;
74
74
  parallelWorkerCount?: number;
75
75
  messageGroupId?: string;
76
+ messageGroup?: string;
77
+ dynamicMessageGroupId?: string;
76
78
  }
77
79
  export interface WorkflowTemplateDeclaration {
78
80
  appIdentifier: string;
@@ -91,6 +93,8 @@ export interface WorkflowTemplate {
91
93
  supportingAppId?: string;
92
94
  parallelWorkerCount?: number;
93
95
  messageGroupId?: string;
96
+ messageGroup?: string;
97
+ dynamicMessageGroupId?: string;
94
98
  createdOn?: Date;
95
99
  updatedOn?: Date;
96
100
  createdById?: string;
@@ -34,7 +34,10 @@ workflows:
34
34
  triggerKey: item|update
35
35
  isActive: true
36
36
  public: true
37
- concurrency: 3 # The maximum number of concurrent jobs to run for this workflow. Defaults to 5
37
+ parallelWorkerCount: 5 # The maximum number of concurrent jobs to run for this workflow (Range 1-30). Defaults to 5
38
+ messageGroupId: # Optional. Used to group jobs in a specific queue. Allows multiple workflows to use the same queue. Not to be used with dynamicMessageGroupId
39
+ messageGroup: # Optional. Used with dynamicMessageGroupId to allow for dynamic queue assignment and allows for multiple workflows to use the same queue
40
+ dynamicMessageGroupId: # Optional. Allows for dynamic queue assignment based on the event data. Example usage: 'family-queue-{newData.itemFamilyId}'
38
41
  workflowDefinition:
39
42
  paths:
40
43
  - conditional: "true"
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.3.4",
2
+ "version": "2.3.5",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
@@ -537,7 +537,14 @@
537
537
  "examples": [
538
538
  "$ contrail project load <TEMPLATE_FILE_LOCATION>\n"
539
539
  ],
540
- "flags": {},
540
+ "flags": {
541
+ "dumpTransformedFile": {
542
+ "name": "dumpTransformedFile",
543
+ "type": "boolean",
544
+ "description": "If set to true, stores a transformed version of the template file",
545
+ "allowNo": false
546
+ }
547
+ },
541
548
  "args": {
542
549
  "templateFileLocation": {
543
550
  "name": "templateFileLocation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "2.3.4",
3
+ "version": "2.3.5",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",