@contrail/cli 1.0.6 → 1.0.7

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/lib/cli.js CHANGED
@@ -2,35 +2,119 @@ import { create } from './apps/create-app/create';
2
2
  import { deployApp, removeApp } from './apps/deploy-app/deploy';
3
3
  import { getTypes } from './types/types-extractor';
4
4
  import { uploadTypes } from './types/types-uploader';
5
+ import { parseOptionSet } from './types/parse-optionSet';
6
+ const cliUsage = require('command-line-usage');
5
7
  /**
6
8
  * Built using documentation here:
7
9
  * https://www.twilio.com/blog/how-to-build-a-cli-with-node-js
8
10
  */
9
11
  export async function cli(args) {
10
- const command = args[2];
11
- switch (command) {
12
- case 'create-app': {
13
- create(args);
14
- break;
15
- }
16
- case 'deploy-app': {
17
- deployApp(args);
18
- break;
19
- }
20
- case 'remove-app': {
21
- removeApp(args);
12
+ const fullCommand = args[2];
13
+ let domain;
14
+ let command;
15
+ const commandParts = fullCommand.split('.');
16
+ if (!(commandParts === null || commandParts === void 0 ? void 0 : commandParts.length)) {
17
+ printIllegalCommand();
18
+ return;
19
+ }
20
+ if ((commandParts === null || commandParts === void 0 ? void 0 : commandParts.length) < 2 && commandParts[0].toLowerCase() !== 'help' && commandParts[0].toLowerCase() !== '-help' && commandParts[0].toLowerCase() !== '--help') {
21
+ printIllegalCommand();
22
+ return;
23
+ }
24
+ domain = commandParts[0];
25
+ command = commandParts[1];
26
+ switch (domain) {
27
+ case 'app': {
28
+ switch (command) {
29
+ case 'create': {
30
+ create(args);
31
+ break;
32
+ }
33
+ case 'deploy': {
34
+ deployApp(args);
35
+ break;
36
+ }
37
+ case 'remove': {
38
+ removeApp(args);
39
+ break;
40
+ }
41
+ default: {
42
+ printIllegalCommand();
43
+ }
44
+ }
22
45
  break;
23
46
  }
24
- case 'extract-types': {
25
- getTypes(args);
47
+ case 'types': {
48
+ switch (command) {
49
+ case 'getAll': {
50
+ getTypes(args);
51
+ break;
52
+ }
53
+ case 'upload': {
54
+ uploadTypes(args);
55
+ break;
56
+ }
57
+ case 'parseOptionSet': {
58
+ parseOptionSet(args);
59
+ break;
60
+ }
61
+ default: {
62
+ printIllegalCommand();
63
+ }
64
+ }
26
65
  break;
27
66
  }
28
- case 'upload-types': {
29
- uploadTypes(args);
67
+ case 'help':
68
+ case '-help':
69
+ case '--help': {
70
+ printHelp();
30
71
  break;
31
72
  }
32
73
  default: {
33
- console.log('Illegal command.');
74
+ printIllegalCommand();
34
75
  }
35
76
  }
36
77
  }
78
+ function printHelp() {
79
+ const sections = [
80
+ {
81
+ header: 'VibeIQ CLI',
82
+ content: 'From your friends at VibeIQ!',
83
+ },
84
+ ...helpSections,
85
+ ];
86
+ const usage = cliUsage(sections);
87
+ console.log(usage);
88
+ }
89
+ function printIllegalCommand() {
90
+ console.log('Illegal command.');
91
+ const usage = cliUsage(helpSections);
92
+ console.log(usage);
93
+ }
94
+ const helpSections = [
95
+ {
96
+ header: 'General Usage',
97
+ content: [
98
+ 'Commands are run by pairing a domain with a command, joining them using a "."',
99
+ 'e.g. app.create',
100
+ '',
101
+ 'All <File Path> options should use the file path relative to the folder the command is being run in and include the file ext.',
102
+ ],
103
+ },
104
+ {
105
+ header: 'App Commands - Domain: app',
106
+ content: [
107
+ 'create Create a new app workspace.',
108
+ 'deploy Deploy your app.',
109
+ 'remove Remove your app.',
110
+ ],
111
+ },
112
+ {
113
+ header: 'Type Commands - Domain: types',
114
+ content: [
115
+ 'getAll Get all OptionSets and Types for your Org.',
116
+ 'upload Upload OptionSets and Types for your Org.',
117
+ 'parseOptionSet <File Path> Parse a CSV file into OptionSets.',
118
+ ],
119
+ },
120
+ ];
@@ -13,7 +13,7 @@ export interface ReducedTypeProperties {
13
13
  label: string;
14
14
  editable: boolean;
15
15
  core: boolean;
16
- typePropertyOptionSetId: string;
16
+ typePropertyOptionSetName: string;
17
17
  formula: string;
18
18
  referencedTypeRootSlug: string;
19
19
  defaultValue: string;
@@ -21,7 +21,6 @@ export interface ReducedTypeProperties {
21
21
  inherited: boolean;
22
22
  }
23
23
  export interface ReducedOptionSet {
24
- id: string;
25
24
  name: string;
26
25
  isArchived: boolean;
27
26
  optionSet: any[];
@@ -0,0 +1,2 @@
1
+ export declare function parseOptionSet(args: any): Promise<void>;
2
+ export declare function parseOptionSets(fileName: any): Promise<void>;
@@ -0,0 +1,79 @@
1
+ import { StringUtil } from '@contrail/util';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs';
4
+ import { getSeparatorByPlatform } from '../common';
5
+ import * as csvToJson from 'csvtojson';
6
+ const yaml = require('js-yaml');
7
+ export async function parseOptionSet(args) {
8
+ try {
9
+ if ((args === null || args === void 0 ? void 0 : args.length) < 4) {
10
+ throw new Error('File name must be included.');
11
+ }
12
+ console.log(`%s Parsing`, chalk.yellow.bold('OPTION SETS'));
13
+ await parseOptionSets(args[3]);
14
+ console.log(`%s Parsed`, chalk.yellow.bold('OPTION SETS'));
15
+ }
16
+ catch (e) {
17
+ console.log(`%s Parsing Failed. ${e}`, chalk.red.bold('FAIL'));
18
+ return;
19
+ }
20
+ console.log('%s Parsing Complete.', chalk.green.bold('COMPLETE'));
21
+ }
22
+ async function loadDataFromCSV(fileName) {
23
+ const CURRENT_DIR = process.cwd();
24
+ try {
25
+ const separator = getSeparatorByPlatform();
26
+ const csvData = await csvToJson.default().fromFile(CURRENT_DIR + separator + fileName);
27
+ return csvData;
28
+ }
29
+ catch (e) {
30
+ throw new Error(`ERROR PARSING CSV: ${e}`);
31
+ }
32
+ }
33
+ export async function parseOptionSets(fileName) {
34
+ const csvData = await loadDataFromCSV(fileName);
35
+ const removedFields = new Set();
36
+ const valuesByFieldName = {};
37
+ for (const row of csvData) {
38
+ for (const [key, value] of Object.entries(row)) {
39
+ // Dont parse boolean fields - later log removed fields
40
+ if (value.toLowerCase() === 'false' || value.toLowerCase() === 'true') {
41
+ removedFields.add(key);
42
+ continue;
43
+ }
44
+ if (!value) {
45
+ continue;
46
+ }
47
+ if (!valuesByFieldName[key]) {
48
+ valuesByFieldName[key] = new Set();
49
+ }
50
+ valuesByFieldName[key].add(value);
51
+ }
52
+ }
53
+ for (const removed of removedFields.values()) {
54
+ console.log(`Removed Boolean Field: ${removed}`);
55
+ }
56
+ const reducedOptionSets = [];
57
+ for (const [key, value] of Object.entries(valuesByFieldName)) {
58
+ const optionSet = [];
59
+ for (const entry of value.values()) {
60
+ optionSet.push({
61
+ value: StringUtil.convertToCamelCase(entry),
62
+ display: isUpperCase(entry) ? entry : StringUtil.convertToTitleCase(entry),
63
+ });
64
+ }
65
+ const reducedOptionSet = {
66
+ name: StringUtil.convertToTitleCase(key),
67
+ isArchived: false,
68
+ optionSet,
69
+ };
70
+ reducedOptionSets.push(reducedOptionSet);
71
+ }
72
+ const yamlStr = yaml.dump(reducedOptionSets);
73
+ const CURRENT_DIR = process.cwd();
74
+ const separator = getSeparatorByPlatform();
75
+ fs.writeFileSync(CURRENT_DIR + separator + 'parsed_option_sets.yml', yamlStr, 'utf8');
76
+ }
77
+ function isUpperCase(str) {
78
+ return str === str.toUpperCase();
79
+ }
@@ -9,10 +9,13 @@ export async function getTypes(args) {
9
9
  const userConfig = await authUser();
10
10
  console.log(`%s Getting all types for orgId: ${userConfig.orgId}`, chalk.blue.bold('START'));
11
11
  console.log(`%s Retrieving`, chalk.yellow.bold('OPTION SETS'));
12
- await getOptionSets();
12
+ const optionSets = await new Entities().get({
13
+ entityName: 'type-property-option-set',
14
+ });
15
+ await getOptionSets(optionSets);
13
16
  console.log(`%s Retrieved`, chalk.yellow.bold('OPTION SETS'));
14
17
  console.log(`%s Retrieving`, chalk.yellow.bold('TYPES'));
15
- await getReducedTypes();
18
+ await getReducedTypes(optionSets);
16
19
  console.log(`%s Retrieved`, chalk.yellow.bold('TYPES'));
17
20
  }
18
21
  catch (e) {
@@ -21,15 +24,11 @@ export async function getTypes(args) {
21
24
  }
22
25
  console.log('%s Types Retrieval Complete.', chalk.green.bold('COMPLETE'));
23
26
  }
24
- async function getOptionSets() {
25
- const optionSets = await new Entities().get({
26
- entityName: 'type-property-option-set',
27
- });
27
+ async function getOptionSets(optionSets) {
28
28
  let reducedOptionSets = [];
29
29
  for (const optionSet of optionSets) {
30
30
  const reducedOptionSet = {
31
31
  name: optionSet.name,
32
- id: optionSet.id,
33
32
  isArchived: optionSet.isArchived,
34
33
  optionSet: optionSet.optionSet,
35
34
  };
@@ -44,7 +43,7 @@ async function getOptionSets() {
44
43
  const yamlStr = yaml.dump(optionSetsHolder);
45
44
  fs.writeFileSync(CURRENT_DIR + separator + 'option_sets.yml', yamlStr, 'utf8');
46
45
  }
47
- async function getReducedTypes() {
46
+ async function getReducedTypes(optionSets) {
48
47
  const types = await new Entities().get({
49
48
  entityName: 'type',
50
49
  });
@@ -56,6 +55,10 @@ async function getReducedTypes() {
56
55
  id: type.id,
57
56
  relations: ['typeProperties'],
58
57
  });
58
+ const optionSetNameById = {};
59
+ for (const optionSet of optionSets) {
60
+ optionSetNameById[optionSet.id] = optionSet.name;
61
+ }
59
62
  const reducedTypeProperties = [];
60
63
  for (const typeProperty of typeWithProperties.typeProperties) {
61
64
  const reducedTypeProperty = {
@@ -66,7 +69,7 @@ async function getReducedTypes() {
66
69
  label: typeProperty.label,
67
70
  editable: typeProperty.editable,
68
71
  core: typeProperty.core,
69
- typePropertyOptionSetId: typeProperty.typePropertyOptionSetId,
72
+ typePropertyOptionSetName: optionSetNameById[typeProperty.typePropertyOptionSetId],
70
73
  formula: typeProperty.formula,
71
74
  referencedTypeRootSlug: typeProperty.referencedTypeRootSlug,
72
75
  defaultValue: typeProperty.defaultValue,
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import fs from 'fs';
5
5
  import { authUser } from '../auth/login';
6
6
  import { getSeparatorByPlatform } from '../common';
7
+ import * as _ from 'lodash';
7
8
  const yaml = require('js-yaml');
8
9
  export async function uploadTypes(args) {
9
10
  try {
@@ -119,6 +120,13 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
119
120
  for (const newPropertyType of newPropertyTypes) {
120
121
  newPropertyTypesBySlug[newPropertyType.slug] = newPropertyType;
121
122
  }
123
+ const optionSets = await new Entities().get({
124
+ entityName: 'type-property-option-set',
125
+ });
126
+ const optionSetIdByName = {};
127
+ for (const optionSet of optionSets) {
128
+ optionSetIdByName[optionSet.name] = optionSet.id;
129
+ }
122
130
  for (const newPropertyType of newPropertyTypes) {
123
131
  try {
124
132
  const found = currentPropertyTypeBySlug[newPropertyType.slug];
@@ -133,7 +141,7 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
133
141
  label: newPropertyType.label,
134
142
  editable: newPropertyType.editable,
135
143
  core: newPropertyType.core,
136
- typePropertyOptionSetId: newPropertyType.typePropertyOptionSetId,
144
+ typePropertyOptionSetId: optionSetIdByName[newPropertyType.typePropertyOptionSetName],
137
145
  formula: newPropertyType.formula,
138
146
  referencedTypeRootSlug: newPropertyType.referencedTypeRootSlug,
139
147
  defaultValue: newPropertyType.defaultValue,
@@ -145,6 +153,26 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
145
153
  },
146
154
  });
147
155
  }
156
+ else if (found && !newPropertyType.inherited && !typePropertiesEqual(found, newPropertyType, optionSetIdByName)) {
157
+ console.log('Updating Type-Property: ', newPropertyType.slug);
158
+ await new Entities().update({
159
+ entityName: 'type-property',
160
+ id: found.id,
161
+ object: {
162
+ isArchived: newPropertyType.isArchived,
163
+ propertyType: newPropertyType.propertyType,
164
+ slug: newPropertyType.slug,
165
+ label: newPropertyType.label,
166
+ editable: newPropertyType.editable,
167
+ core: newPropertyType.core,
168
+ typePropertyOptionSetId: optionSetIdByName[newPropertyType.typePropertyOptionSetName],
169
+ formula: newPropertyType.formula,
170
+ referencedTypeRootSlug: newPropertyType.referencedTypeRootSlug,
171
+ defaultValue: newPropertyType.defaultValue,
172
+ propertyLevel: newPropertyType.propertyLevel,
173
+ },
174
+ });
175
+ }
148
176
  }
149
177
  catch (err) {
150
178
  console.log('Error Creating Properties: ', err);
@@ -169,3 +197,32 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
169
197
  }
170
198
  }
171
199
  }
200
+ function typePropertiesEqual(typeProperty1, typeProperty2, optionSetIdByName) {
201
+ const reducedProperty1 = {
202
+ isArchived: typeProperty1.isArchived,
203
+ propertyType: typeProperty1.propertyType,
204
+ slug: typeProperty1.slug,
205
+ label: typeProperty1.label,
206
+ editable: typeProperty1.editable,
207
+ core: typeProperty1.core,
208
+ typePropertyOptionSetId: optionSetIdByName[typeProperty1.typePropertyOptionSetName],
209
+ formula: typeProperty1.formula,
210
+ referencedTypeRootSlug: typeProperty1.referencedTypeRootSlug,
211
+ defaultValue: typeProperty1.defaultValue,
212
+ propertyLevel: typeProperty1.propertyLevel,
213
+ };
214
+ const reducedProperty2 = {
215
+ isArchived: typeProperty2.isArchived,
216
+ propertyType: typeProperty2.propertyType,
217
+ slug: typeProperty2.slug,
218
+ label: typeProperty2.label,
219
+ editable: typeProperty2.editable,
220
+ core: typeProperty2.core,
221
+ typePropertyOptionSetId: optionSetIdByName[typeProperty2.typePropertyOptionSetName],
222
+ formula: typeProperty2.formula,
223
+ referencedTypeRootSlug: typeProperty2.referencedTypeRootSlug,
224
+ defaultValue: typeProperty2.defaultValue,
225
+ propertyLevel: typeProperty2.propertyLevel,
226
+ };
227
+ return _.isEqual(reducedProperty1, reducedProperty2);
228
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -24,27 +24,32 @@
24
24
  "license": "ISC",
25
25
  "dependencies": {
26
26
  "@contrail/sdk": "^1.0.91",
27
+ "@contrail/util": "^1.0.17",
27
28
  "amazon-cognito-identity-js": "^5.2.3",
28
29
  "arg": "^4.0.1",
29
30
  "aws-sdk": "^2.1044.0",
30
31
  "chalk": "^4.0.0",
32
+ "command-line-usage": "^6.1.1",
33
+ "csvtojson": "^2.0.10",
31
34
  "esm": "^3.2.25",
32
35
  "execa": "^5.0.0",
33
36
  "inquirer": "^8.2.0",
34
37
  "js-yaml": "^4.1.0",
35
38
  "listr": "^0.14.3",
39
+ "lodash": "^4.17.21",
36
40
  "ncp": "^2.0.0",
37
41
  "pkg-install": "^1.0.0"
38
42
  },
39
43
  "devDependencies": {
40
44
  "@types/jest": "^23.3.14",
45
+ "@types/lodash": "^4.14.178",
46
+ "@types/node": "^14.6.4",
41
47
  "jest": "^23.6.0",
42
48
  "prettier": "^1.19.1",
43
49
  "ts-jest": "^23.10.5",
44
50
  "tslint": "^5.11.0",
45
51
  "tslint-config-prettier": "^1.18.0",
46
- "typescript": "^3.0.1",
47
- "@types/node": "^14.6.4"
52
+ "typescript": "^3.0.1"
48
53
  },
49
54
  "jest": {
50
55
  "moduleFileExtensions": [
package/src/cli.ts CHANGED
@@ -3,36 +3,130 @@ import { create } from './apps/create-app/create';
3
3
  import { deployApp, removeApp } from './apps/deploy-app/deploy';
4
4
  import { getTypes } from './types/types-extractor';
5
5
  import { uploadTypes } from './types/types-uploader';
6
+ import { parseOptionSet } from './types/parse-optionSet';
7
+ const cliUsage = require('command-line-usage');
8
+
6
9
  /**
7
10
  * Built using documentation here:
8
11
  * https://www.twilio.com/blog/how-to-build-a-cli-with-node-js
9
12
  */
10
13
 
11
14
  export async function cli(args) {
12
- const command = args[2];
13
- switch (command) {
14
- case 'create-app': {
15
- create(args);
16
- break;
17
- }
18
- case 'deploy-app': {
19
- deployApp(args);
20
- break;
21
- }
22
- case 'remove-app': {
23
- removeApp(args);
15
+ const fullCommand = args[2];
16
+ let domain;
17
+ let command;
18
+ const commandParts = fullCommand.split('.');
19
+
20
+ if (!commandParts?.length) {
21
+ printIllegalCommand();
22
+ return;
23
+ }
24
+
25
+ if (commandParts?.length < 2 && commandParts[0].toLowerCase() !== 'help' && commandParts[0].toLowerCase() !== '-help' && commandParts[0].toLowerCase() !== '--help') {
26
+ printIllegalCommand();
27
+ return;
28
+ }
29
+
30
+ domain = commandParts[0];
31
+ command = commandParts[1];
32
+
33
+ switch (domain) {
34
+ case 'app': {
35
+ switch (command) {
36
+ case 'create': {
37
+ create(args);
38
+ break;
39
+ }
40
+ case 'deploy': {
41
+ deployApp(args);
42
+ break;
43
+ }
44
+ case 'remove': {
45
+ removeApp(args);
46
+ break;
47
+ }
48
+ default: {
49
+ printIllegalCommand();
50
+ }
51
+ }
24
52
  break;
25
53
  }
26
- case 'extract-types': {
27
- getTypes(args);
54
+ case 'types': {
55
+ switch (command) {
56
+ case 'getAll': {
57
+ getTypes(args);
58
+ break;
59
+ }
60
+ case 'upload': {
61
+ uploadTypes(args);
62
+ break;
63
+ }
64
+ case 'parseOptionSet': {
65
+ parseOptionSet(args);
66
+ break;
67
+ }
68
+ default: {
69
+ printIllegalCommand();
70
+ }
71
+ }
28
72
  break;
29
73
  }
30
- case 'upload-types': {
31
- uploadTypes(args);
74
+ case 'help':
75
+ case '-help':
76
+ case '--help': {
77
+ printHelp();
32
78
  break;
33
79
  }
34
80
  default: {
35
- console.log('Illegal command.');
81
+ printIllegalCommand();
36
82
  }
37
83
  }
38
84
  }
85
+
86
+ function printHelp() {
87
+ const sections = [
88
+ {
89
+ header: 'VibeIQ CLI',
90
+ content: 'From your friends at VibeIQ!',
91
+ },
92
+ ...helpSections,
93
+ ];
94
+
95
+ const usage = cliUsage(sections);
96
+ console.log(usage);
97
+ }
98
+
99
+ function printIllegalCommand() {
100
+ console.log('Illegal command.');
101
+
102
+ const usage = cliUsage(helpSections);
103
+ console.log(usage);
104
+ }
105
+
106
+ const helpSections = [
107
+ {
108
+ header: 'General Usage',
109
+ content: [
110
+ 'Commands are run by pairing a domain with a command, joining them using a "."',
111
+ 'e.g. app.create',
112
+ '',
113
+ 'All <File Path> options should use the file path relative to the folder the command is being run in and include the file ext.',
114
+ ],
115
+ },
116
+ {
117
+ header: 'App Commands - Domain: app',
118
+ content: [
119
+ 'create Create a new app workspace.',
120
+ 'deploy Deploy your app.',
121
+ 'remove Remove your app.',
122
+ ],
123
+ },
124
+ {
125
+ header: 'Type Commands - Domain: types',
126
+ content: [
127
+ 'getAll Get all OptionSets and Types for your Org.',
128
+ 'upload Upload OptionSets and Types for your Org.',
129
+ 'parseOptionSet <File Path> Parse a CSV file into OptionSets.',
130
+ ],
131
+ },
132
+ ];
@@ -14,7 +14,7 @@ export interface ReducedTypeProperties {
14
14
  label: string;
15
15
  editable: boolean;
16
16
  core: boolean;
17
- typePropertyOptionSetId: string;
17
+ typePropertyOptionSetName: string;
18
18
  formula: string;
19
19
  referencedTypeRootSlug: string;
20
20
  defaultValue: string;
@@ -23,7 +23,6 @@ export interface ReducedTypeProperties {
23
23
  }
24
24
 
25
25
  export interface ReducedOptionSet {
26
- id: string;
27
26
  name: string;
28
27
  isArchived: boolean;
29
28
  optionSet: any[];
@@ -0,0 +1,96 @@
1
+ import { StringUtil } from '@contrail/util';
2
+ import { ReducedOptionSet } from './interfaces';
3
+ import chalk from 'chalk';
4
+ import fs from 'fs';
5
+ import { getSeparatorByPlatform } from '../common';
6
+ import * as csvToJson from 'csvtojson';
7
+
8
+ const yaml = require('js-yaml');
9
+
10
+ export async function parseOptionSet(args) {
11
+ try {
12
+ if (args?.length < 4) {
13
+ throw new Error('File name must be included.');
14
+ }
15
+ console.log(`%s Parsing`, chalk.yellow.bold('OPTION SETS'));
16
+ await parseOptionSets(args[3]);
17
+ console.log(`%s Parsed`, chalk.yellow.bold('OPTION SETS'));
18
+
19
+ }
20
+ catch (e) {
21
+ console.log(`%s Parsing Failed. ${e}`, chalk.red.bold('FAIL'));
22
+ return;
23
+ }
24
+
25
+ console.log('%s Parsing Complete.', chalk.green.bold('COMPLETE'));
26
+ }
27
+
28
+ async function loadDataFromCSV(fileName) {
29
+ const CURRENT_DIR = process.cwd();
30
+ try {
31
+ const separator = getSeparatorByPlatform();
32
+ const csvData = await csvToJson.default().fromFile(CURRENT_DIR + separator + fileName);
33
+
34
+ return csvData;
35
+ } catch (e) {
36
+ throw new Error(`ERROR PARSING CSV: ${e}`);
37
+ }
38
+ }
39
+
40
+ export async function parseOptionSets(fileName) {
41
+ const csvData = await loadDataFromCSV(fileName);
42
+
43
+ const removedFields = new Set<string>();
44
+ const valuesByFieldName = {};
45
+ for (const row of csvData) {
46
+ for (const [key, value] of Object.entries(row)) {
47
+
48
+ // Dont parse boolean fields - later log removed fields
49
+ if ((value as string).toLowerCase() === 'false' || (value as string).toLowerCase() === 'true') {
50
+ removedFields.add(key);
51
+ continue;
52
+ }
53
+
54
+ if (!value) {
55
+ continue;
56
+ }
57
+
58
+ if (!valuesByFieldName[key]) {
59
+ valuesByFieldName[key] = new Set<string>();
60
+ }
61
+ (valuesByFieldName[key] as Set<string>).add(value as string);
62
+ }
63
+ }
64
+
65
+ for (const removed of removedFields.values()) {
66
+ console.log(`Removed Boolean Field: ${removed}`);
67
+ }
68
+
69
+ const reducedOptionSets = [];
70
+ for (const [key, value] of Object.entries(valuesByFieldName)) {
71
+ const optionSet = [];
72
+ for (const entry of (value as Set<string>).values()) {
73
+ optionSet.push({
74
+ value: StringUtil.convertToCamelCase(entry),
75
+ display: isUpperCase(entry) ? entry : StringUtil.convertToTitleCase(entry), // if all uppercase dont convert
76
+ });
77
+ }
78
+
79
+ const reducedOptionSet: ReducedOptionSet = {
80
+ name: StringUtil.convertToTitleCase(key),
81
+ isArchived: false,
82
+ optionSet,
83
+ };
84
+
85
+ reducedOptionSets.push(reducedOptionSet);
86
+ }
87
+
88
+ const yamlStr = yaml.dump(reducedOptionSets);
89
+ const CURRENT_DIR = process.cwd();
90
+ const separator = getSeparatorByPlatform();
91
+ fs.writeFileSync(CURRENT_DIR + separator + 'parsed_option_sets.yml', yamlStr, 'utf8');
92
+ }
93
+
94
+ function isUpperCase(str) {
95
+ return str === str.toUpperCase();
96
+ }
@@ -11,13 +11,18 @@ export async function getTypes(args) {
11
11
  try {
12
12
  const userConfig = await authUser();
13
13
  console.log(`%s Getting all types for orgId: ${userConfig.orgId}`, chalk.blue.bold('START'));
14
-
15
14
  console.log(`%s Retrieving`, chalk.yellow.bold('OPTION SETS'));
16
- await getOptionSets();
17
- console.log(`%s Retrieved`, chalk.yellow.bold('OPTION SETS'));
18
15
 
16
+ const optionSets = await new Entities().get({
17
+ entityName: 'type-property-option-set',
18
+ });
19
+ await getOptionSets(optionSets);
20
+
21
+ console.log(`%s Retrieved`, chalk.yellow.bold('OPTION SETS'));
19
22
  console.log(`%s Retrieving`, chalk.yellow.bold('TYPES'));
20
- await getReducedTypes();
23
+
24
+ await getReducedTypes(optionSets);
25
+
21
26
  console.log(`%s Retrieved`, chalk.yellow.bold('TYPES'));
22
27
 
23
28
  }
@@ -29,16 +34,11 @@ export async function getTypes(args) {
29
34
  console.log('%s Types Retrieval Complete.', chalk.green.bold('COMPLETE'));
30
35
  }
31
36
 
32
- async function getOptionSets() {
33
- const optionSets = await new Entities().get({
34
- entityName: 'type-property-option-set',
35
- });
36
-
37
+ async function getOptionSets(optionSets) {
37
38
  let reducedOptionSets = [];
38
39
  for (const optionSet of optionSets) {
39
40
  const reducedOptionSet: ReducedOptionSet = {
40
41
  name: optionSet.name,
41
- id: optionSet.id,
42
42
  isArchived: optionSet.isArchived,
43
43
  optionSet: optionSet.optionSet,
44
44
  };
@@ -59,7 +59,7 @@ async function getOptionSets() {
59
59
  fs.writeFileSync(CURRENT_DIR + separator + 'option_sets.yml', yamlStr, 'utf8');
60
60
  }
61
61
 
62
- async function getReducedTypes() {
62
+ async function getReducedTypes(optionSets) {
63
63
  const types = await new Entities().get({
64
64
  entityName: 'type',
65
65
  });
@@ -74,6 +74,11 @@ async function getReducedTypes() {
74
74
  relations: ['typeProperties'],
75
75
  });
76
76
 
77
+ const optionSetNameById = {};
78
+ for (const optionSet of optionSets) {
79
+ optionSetNameById[optionSet.id] = optionSet.name;
80
+ }
81
+
77
82
  const reducedTypeProperties = [];
78
83
  for (const typeProperty of typeWithProperties.typeProperties) {
79
84
  const reducedTypeProperty: ReducedTypeProperties = {
@@ -84,7 +89,7 @@ async function getReducedTypes() {
84
89
  label: typeProperty.label,
85
90
  editable: typeProperty.editable,
86
91
  core: typeProperty.core,
87
- typePropertyOptionSetId: typeProperty.typePropertyOptionSetId,
92
+ typePropertyOptionSetName: optionSetNameById[typeProperty.typePropertyOptionSetId],
88
93
  formula: typeProperty.formula,
89
94
  referencedTypeRootSlug: typeProperty.referencedTypeRootSlug,
90
95
  defaultValue: typeProperty.defaultValue,
@@ -5,6 +5,7 @@ import chalk from 'chalk';
5
5
  import fs from 'fs';
6
6
  import { authUser } from '../auth/login';
7
7
  import { getSeparatorByPlatform } from '../common';
8
+ import * as _ from 'lodash';
8
9
 
9
10
  const yaml = require('js-yaml');
10
11
 
@@ -141,6 +142,15 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
141
142
  newPropertyTypesBySlug[newPropertyType.slug] = newPropertyType;
142
143
  }
143
144
 
145
+ const optionSets = await new Entities().get({
146
+ entityName: 'type-property-option-set',
147
+ });
148
+
149
+ const optionSetIdByName = {};
150
+ for (const optionSet of optionSets) {
151
+ optionSetIdByName[optionSet.name] = optionSet.id;
152
+ }
153
+
144
154
  for (const newPropertyType of newPropertyTypes) {
145
155
  try {
146
156
  const found = currentPropertyTypeBySlug[newPropertyType.slug];
@@ -158,7 +168,7 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
158
168
  label: newPropertyType.label,
159
169
  editable: newPropertyType.editable,
160
170
  core: newPropertyType.core,
161
- typePropertyOptionSetId: newPropertyType.typePropertyOptionSetId,
171
+ typePropertyOptionSetId: optionSetIdByName[newPropertyType.typePropertyOptionSetName],
162
172
  formula: newPropertyType.formula,
163
173
  referencedTypeRootSlug: newPropertyType.referencedTypeRootSlug,
164
174
  defaultValue: newPropertyType.defaultValue,
@@ -170,6 +180,26 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
170
180
  },
171
181
  });
172
182
  }
183
+ else if (found && !newPropertyType.inherited && !typePropertiesEqual(found, newPropertyType, optionSetIdByName)) {
184
+ console.log('Updating Type-Property: ', newPropertyType.slug);
185
+ await new Entities().update({
186
+ entityName: 'type-property',
187
+ id: found.id,
188
+ object: {
189
+ isArchived: newPropertyType.isArchived,
190
+ propertyType: newPropertyType.propertyType,
191
+ slug: newPropertyType.slug,
192
+ label: newPropertyType.label,
193
+ editable: newPropertyType.editable,
194
+ core: newPropertyType.core,
195
+ typePropertyOptionSetId: optionSetIdByName[newPropertyType.typePropertyOptionSetName],
196
+ formula: newPropertyType.formula,
197
+ referencedTypeRootSlug: newPropertyType.referencedTypeRootSlug,
198
+ defaultValue: newPropertyType.defaultValue,
199
+ propertyLevel: newPropertyType.propertyLevel,
200
+ },
201
+ });
202
+ }
173
203
 
174
204
  } catch (err) {
175
205
  console.log('Error Creating Properties: ', err);
@@ -195,4 +225,37 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
195
225
  throw new Error(`Error Deleting Properties: ${err}`);
196
226
  }
197
227
  }
228
+ }
229
+
230
+ function typePropertiesEqual(typeProperty1, typeProperty2, optionSetIdByName): boolean {
231
+
232
+ const reducedProperty1 = {
233
+ isArchived: typeProperty1.isArchived,
234
+ propertyType: typeProperty1.propertyType,
235
+ slug: typeProperty1.slug,
236
+ label: typeProperty1.label,
237
+ editable: typeProperty1.editable,
238
+ core: typeProperty1.core,
239
+ typePropertyOptionSetId: optionSetIdByName[typeProperty1.typePropertyOptionSetName],
240
+ formula: typeProperty1.formula,
241
+ referencedTypeRootSlug: typeProperty1.referencedTypeRootSlug,
242
+ defaultValue: typeProperty1.defaultValue,
243
+ propertyLevel: typeProperty1.propertyLevel,
244
+ };
245
+
246
+ const reducedProperty2 = {
247
+ isArchived: typeProperty2.isArchived,
248
+ propertyType: typeProperty2.propertyType,
249
+ slug: typeProperty2.slug,
250
+ label: typeProperty2.label,
251
+ editable: typeProperty2.editable,
252
+ core: typeProperty2.core,
253
+ typePropertyOptionSetId: optionSetIdByName[typeProperty2.typePropertyOptionSetName],
254
+ formula: typeProperty2.formula,
255
+ referencedTypeRootSlug: typeProperty2.referencedTypeRootSlug,
256
+ defaultValue: typeProperty2.defaultValue,
257
+ propertyLevel: typeProperty2.propertyLevel,
258
+ };
259
+
260
+ return _.isEqual(reducedProperty1, reducedProperty2);
198
261
  }