@contrail/cli 2.2.6 → 2.2.8

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.2.6 darwin-arm64 node-v18.13.0
20
+ @contrail/cli/2.2.8 darwin-arm64 node-v18.13.0
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -182,6 +182,7 @@ async function uploadTypePolicyLinks(reducedTypePolicyLinks) {
182
182
  if (!transformReducedLink(loadedTPLink, types, currentTypePolicies)) {
183
183
  continue;
184
184
  }
185
+ logger_1.default.info(`Creating: ${loadedTPLink.typeSlug}|${loadedTPLink.typePolicyIdentifier}`);
185
186
  await new sdk_1.Entities().create({
186
187
  entityName: 'type-policy-link',
187
188
  object: {
@@ -8,3 +8,4 @@ export default class UploadTypePropertyPolicies extends BaseCommand {
8
8
  run(): Promise<void>;
9
9
  }
10
10
  export declare function uploadTypePropertyPolicies(dir: string): Promise<void>;
11
+ export declare function findAllOccurrences(array: any, evaluator: any): any[];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadTypePropertyPolicies = void 0;
3
+ exports.findAllOccurrences = exports.uploadTypePropertyPolicies = 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"));
@@ -13,6 +13,7 @@ const users_1 = require("../../api/users");
13
13
  const basecommand_1 = require("../../basecommand");
14
14
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
15
15
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
16
+ const _ = tslib_1.__importStar(require("lodash"));
16
17
  class UploadTypePropertyPolicies extends basecommand_1.BaseCommand {
17
18
  async run() {
18
19
  const { flags } = await this.parse(UploadTypePropertyPolicies);
@@ -172,75 +173,115 @@ async function uploadTypePropertyPolicyLinks(reducedTypePropertyPolicyLinks) {
172
173
  const currentTPPLinks = await new sdk_1.Entities().get({
173
174
  entityName: 'type-property-policy-link',
174
175
  });
176
+ logger_1.default.info(`found ${currentTPPLinks.length} current type property policy links`);
177
+ logger_1.default.info(`found ${reducedTypePropertyPolicyLinks.length} type property policy links to load`);
175
178
  const currentLinksHashes = {};
176
179
  for (const currentLink of currentTPPLinks) {
177
- const foundTypeProperty = typeProperties.find((tp) => tp.id === currentLink.typePropertyId);
178
- const foundTPP = currentTPPs.find((tpp) => tpp.id === currentLink.typePropertyPolicyId);
179
- if (!foundTypeProperty || !foundTPP) {
180
- logger_1.default.info(`skipping unhashable, invalid policy link: ${JSON.stringify(currentLink, null, 2)}`);
181
- continue;
180
+ const foundTPs = findAllOccurrences(typeProperties, (tp) => tp.id === currentLink.typePropertyId);
181
+ for (const foundTP of foundTPs) {
182
+ const foundTPP = currentTPPs.find((tpp) => tpp.id === currentLink.typePropertyPolicyId);
183
+ if (!foundTP || !foundTPP) {
184
+ logger_1.default.info(`skipping unhashable, invalid policy link: ${JSON.stringify(currentLink, null, 2)}`);
185
+ continue;
186
+ }
187
+ const hash = `${foundTP.typeSlug}|${foundTP.slug}|${foundTPP.identifier}`;
188
+ currentLinksHashes[hash] = currentLink;
182
189
  }
183
- const hash = `${foundTypeProperty.typeSlug}${foundTypeProperty.slug}|${foundTPP.identifier}`;
184
- currentLinksHashes[hash] = currentLink;
185
190
  }
191
+ logger_1.default.debug(`current links hashes: ${JSON.stringify(currentLinksHashes, null, 2)}`);
186
192
  const reducedLinksByHashes = {};
187
193
  for (const loadedLink of reducedTypePropertyPolicyLinks) {
188
194
  const hash = `${loadedLink.typeSlug}|${loadedLink.typePropertySlug}|${loadedLink.typePropertyPolicyIdentifier}`;
189
195
  reducedLinksByHashes[hash] = loadedLink;
190
196
  }
191
- for (const loadedTPPLink of reducedTypePropertyPolicyLinks) {
197
+ const toCreate = Object.keys(reducedLinksByHashes)
198
+ .filter((x) => !currentLinksHashes[x])
199
+ .map((x) => {
200
+ return {
201
+ [x]: reducedLinksByHashes[x],
202
+ };
203
+ })
204
+ .reduce((a, b) => Object.assign(a, b), {});
205
+ const toDelete = _.uniqBy(Object.keys(currentLinksHashes)
206
+ .filter((x) => !reducedLinksByHashes[x])
207
+ .map((x) => {
208
+ return {
209
+ [x]: currentLinksHashes[x],
210
+ };
211
+ }), 'id').reduce((a, b) => Object.assign(a, b), {});
212
+ logger_1.default.debug(`reduced links hashes: ${JSON.stringify(reducedLinksByHashes, null, 2)}`);
213
+ logger_1.default.info(`found ${Object.keys(toCreate).length} type property policy links to create`);
214
+ logger_1.default.info(`found ${Object.keys(toDelete).length} type property policy links to delete`);
215
+ logger_1.default.debug(`to create: ${JSON.stringify(toCreate, null, 2)}`);
216
+ logger_1.default.debug(`to delete: ${JSON.stringify(toDelete, null, 2)}`);
217
+ logger_1.default.debug(`current links hashes to delete: ${JSON.stringify(Object.keys(currentLinksHashes).filter((x) => !reducedLinksByHashes[x]), null, 2)}`);
218
+ // fs.writeFileSync('toCreate.json', JSON.stringify(toCreate, null, 2));
219
+ // fs.writeFileSync('toDelete.json', JSON.stringify(toDelete, null, 2));
220
+ // fs.writeFileSync('currentLinksHashes.json', JSON.stringify(currentLinksHashes, null, 2));
221
+ // fs.writeFileSync('reducedLinksByHashes.json', JSON.stringify(reducedLinksByHashes, null, 2));
222
+ // fs.writeFileSync(
223
+ // 'current_link_hashes_to_delete.json',
224
+ // JSON.stringify(
225
+ // Object.keys(currentLinksHashes).filter((x) => !reducedLinksByHashes[x]),
226
+ // null,
227
+ // 2,
228
+ // ),
229
+ // );
230
+ // return;
231
+ for (const [hash, link] of Object.entries(toCreate)) {
192
232
  try {
193
- const found = currentLinksHashes[`${loadedTPPLink.typeSlug}|${loadedTPPLink.typePropertySlug}|${loadedTPPLink.typePropertyPolicyIdentifier}`];
194
- if (!found) {
195
- if (!transformReducedLink(loadedTPPLink, typeProperties, currentTPPs)) {
196
- continue;
197
- }
198
- await new sdk_1.Entities().create({
199
- entityName: 'type-property-policy-link',
200
- object: {
201
- typePropertyPolicyId: loadedTPPLink.typePropertyPolicyId,
202
- typePropertyId: loadedTPPLink.typePropertyId,
203
- },
204
- });
205
- }
206
- else {
207
- if (!transformReducedLink(loadedTPPLink, typeProperties, currentTPPs)) {
208
- continue;
209
- }
233
+ if (!transformReducedLink(link, typeProperties, currentTPPs)) {
234
+ continue;
210
235
  }
236
+ logger_1.default.info(`Creating: ${hash}: ${JSON.stringify(link, null, 2)}`);
237
+ await new sdk_1.Entities().create({
238
+ entityName: 'type-property-policy-link',
239
+ object: {
240
+ typePropertyPolicyId: link.typePropertyPolicyId,
241
+ typePropertyId: link.typePropertyId,
242
+ },
243
+ });
211
244
  }
212
245
  catch (err) {
213
246
  logger_1.default.error(`Error Uploading type property policy links: ${err}`);
214
247
  }
215
248
  }
216
- for (const [currentLinkHash, currentLink] of Object.entries(currentLinksHashes)) {
249
+ for (const [hash, link] of Object.entries(toDelete)) {
217
250
  try {
218
- const found = reducedLinksByHashes[currentLinkHash];
219
- if (!found) {
220
- logger_1.default.info(`Deleting: ${currentLinkHash}`);
221
- await new sdk_1.Entities().delete({
222
- entityName: 'type-property-policy-link',
223
- id: currentLink['id'],
224
- });
225
- }
251
+ logger_1.default.info(`Deleting: ${hash}`);
252
+ await new sdk_1.Entities().delete({
253
+ entityName: 'type-property-policy-link',
254
+ id: link['id'],
255
+ });
226
256
  }
227
257
  catch (err) {
228
258
  logger_1.default.error(`Error Deleting type property policy link: ${err}`);
229
259
  }
230
260
  }
261
+ function transformReducedLink(TPPLink, typeProperties, typePropertyPolicies) {
262
+ const foundTP = typeProperties.find((tp) => tp.slug === TPPLink.typePropertySlug && tp.typeSlug === TPPLink.typeSlug);
263
+ if (!foundTP) {
264
+ logger_1.default.error(`Could not find type property with slug ${TPPLink.typePropertySlug} `);
265
+ return false;
266
+ }
267
+ TPPLink.typePropertyId = foundTP.id;
268
+ const foundTPP = typePropertyPolicies.find((tpp) => tpp.identifier === TPPLink.typePropertyPolicyIdentifier);
269
+ if (!foundTPP) {
270
+ logger_1.default.error(`Could not find type property policy with identifier ${TPPLink.typePropertyPolicyIdentifier} `);
271
+ return false;
272
+ }
273
+ TPPLink.typePropertyPolicyId = foundTPP.id;
274
+ return true;
275
+ }
231
276
  }
232
- function transformReducedLink(TPPLink, typeProperties, typePropertyPolicies) {
233
- const foundTP = typeProperties.find((tp) => tp.slug === TPPLink.typePropertySlug && tp.typeSlug === TPPLink.typeSlug);
234
- if (!foundTP) {
235
- logger_1.default.error(`Could not find type property with slug ${TPPLink.typePropertySlug}`);
236
- return false;
237
- }
238
- TPPLink.typePropertyId = foundTP.id;
239
- const foundTPP = typePropertyPolicies.find((tpp) => tpp.identifier === TPPLink.typePropertyPolicyIdentifier);
240
- if (!foundTPP) {
241
- logger_1.default.error(`Could not find type property policy with identifier ${TPPLink.typePropertyPolicyIdentifier}`);
242
- return false;
243
- }
244
- TPPLink.typePropertyPolicyId = foundTPP.id;
245
- return true;
277
+ function findAllOccurrences(array, evaluator) {
278
+ const found = [];
279
+ for (let i = 0; i < array.length; i++) {
280
+ const element = array[i];
281
+ if (evaluator(element)) {
282
+ found.push(element);
283
+ }
284
+ }
285
+ return found;
246
286
  }
287
+ exports.findAllOccurrences = findAllOccurrences;
@@ -40,7 +40,9 @@ function visibilityValidator(appDeclaration) {
40
40
  error: 'Invalid visibility. Visibility must be "public" or "private".',
41
41
  };
42
42
  }
43
- if (appDeclaration.visibility === 'private' && appDeclaration.hidden !== true) {
43
+ if (appDeclaration.visibility === 'private' &&
44
+ Object.prototype.hasOwnProperty.call(appDeclaration, 'hidden') &&
45
+ appDeclaration.hidden === true) {
44
46
  return {
45
47
  error: 'Apps can not be private and hidden.',
46
48
  };
@@ -65,3 +65,41 @@ it('should return an error if app version has invalid format', () => {
65
65
  const validationResult = (0, app_config_validators_1.appVersionValidator)(appDeclaration);
66
66
  expect(validationResult).toEqual(expectedError);
67
67
  });
68
+ describe('visibilityValidator', () => {
69
+ it('should return an error if visibility is neither "public" nor "private"', () => {
70
+ const appDeclaration = {
71
+ visibility: 'invalidVisibility',
72
+ };
73
+ const expectedError = {
74
+ error: 'Invalid visibility. Visibility must be "public" or "private".',
75
+ };
76
+ const validationResult = (0, app_config_validators_1.visibilityValidator)(appDeclaration);
77
+ expect(validationResult).toEqual(expectedError);
78
+ });
79
+ it('should return an error if app is private and not hidden', () => {
80
+ const appDeclaration = {
81
+ visibility: 'private',
82
+ hidden: true,
83
+ };
84
+ const expectedError = {
85
+ error: 'Apps can not be private and hidden.',
86
+ };
87
+ const validationResult = (0, app_config_validators_1.visibilityValidator)(appDeclaration);
88
+ expect(validationResult).toEqual(expectedError);
89
+ });
90
+ it('should not return an error if visibility is "public"', () => {
91
+ const appDeclaration = {
92
+ visibility: 'public',
93
+ };
94
+ const validationResult = (0, app_config_validators_1.visibilityValidator)(appDeclaration);
95
+ expect(validationResult).toBeUndefined();
96
+ });
97
+ it('should not return an error if app is private and hidden', () => {
98
+ const appDeclaration = {
99
+ visibility: 'private',
100
+ hidden: false,
101
+ };
102
+ const validationResult = (0, app_config_validators_1.visibilityValidator)(appDeclaration);
103
+ expect(validationResult).toBeUndefined();
104
+ });
105
+ });
@@ -1 +1,18 @@
1
1
  export declare function uploadTypes(dir: string): Promise<void>;
2
+ export interface ValueDiff {
3
+ adds: any[];
4
+ updates: any[];
5
+ deletions: any[];
6
+ }
7
+ /**
8
+ * compares the keys between different objects and returns the differences
9
+ * captures in adds, updates, and deletions. Object 1 is considered the
10
+ * original, and object 2 is considered the "edit".
11
+ *
12
+ * An add is a property that exists in object2 but not in 1. An edit
13
+ * is a property that exists in both object 1 and 2, but with different values.
14
+ * A deletion is a property in object 1 but not in object 2.
15
+ * @param object1 any object
16
+ * @param object2 any other object
17
+ */
18
+ export declare function extractValueDifferences(object1: object, object2: object, keysToIgnore: Array<string>): ValueDiff;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadTypes = void 0;
3
+ exports.extractValueDifferences = exports.uploadTypes = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const sdk_1 = require("@contrail/sdk");
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
@@ -61,6 +61,13 @@ async function uploadReducedTypes(dir) {
61
61
  for (const type of types) {
62
62
  newTypesBySlug[type.slug] = type;
63
63
  }
64
+ const optionSets = await new sdk_1.Entities().get({
65
+ entityName: 'type-property-option-set',
66
+ });
67
+ if (logger_1.default.isDebugEnabled()) {
68
+ fs_1.default.writeFileSync('reducedTypes.json', JSON.stringify(types, null, 2));
69
+ fs_1.default.writeFileSync('currentTypes.json', JSON.stringify(currentTypes, null, 2));
70
+ }
64
71
  for (const type of types) {
65
72
  try {
66
73
  const found = currentTypesBySlug[type.slug];
@@ -81,21 +88,47 @@ async function uploadReducedTypes(dir) {
81
88
  });
82
89
  const newTypeProperties = (await new sdk_1.Types().getType({ id: createdType.id }))
83
90
  .typeProperties;
84
- await uploadReducedTypeProperties(createdType, newTypeProperties, type.typeProperties);
91
+ await uploadReducedTypeProperties(createdType, newTypeProperties, type.typeProperties, optionSets);
85
92
  }
86
93
  else {
87
- logger_1.default.info(`Updating Type: ${type.slug}`);
88
- const updatedType = await new sdk_1.Entities().update({
89
- entityName: 'type',
90
- id: found.id,
91
- object: {
92
- slug: type.slug,
93
- label: type.label,
94
- typePath: type.typePath,
95
- isArchived: type.isArchived,
96
- },
97
- });
98
- await uploadReducedTypeProperties(updatedType, found.typeProperties, type.typeProperties);
94
+ const proposedType = {
95
+ slug: type.slug,
96
+ label: type.label,
97
+ typePath: type.typePath,
98
+ isArchived: type.isArchived,
99
+ };
100
+ const differences = extractValueDifferences(found, proposedType, [
101
+ 'id',
102
+ 'createdOn',
103
+ 'updatedOn',
104
+ 'createdById',
105
+ 'updatedById',
106
+ 'orgId',
107
+ 'typeRootId',
108
+ 'parentId',
109
+ 'typePolicyIds',
110
+ 'typePolicies',
111
+ 'typeProperties',
112
+ 'typePropertyPolicies',
113
+ 'typeRuleSets',
114
+ 'typeOptionSetHierarchyLinks',
115
+ 'relations',
116
+ 'typeInterfaces',
117
+ ]);
118
+ let updatedType = found;
119
+ if (differences.updates.length || differences.adds.length || differences.deletions.length) {
120
+ logger_1.default.debug(`found differences: ${JSON.stringify(differences, null, 2)}`);
121
+ logger_1.default.info(`Updating Type: ${type.slug}`);
122
+ updatedType = await new sdk_1.Entities().update({
123
+ entityName: 'type',
124
+ id: found.id,
125
+ object: proposedType,
126
+ });
127
+ }
128
+ else {
129
+ logger_1.default.debug(`Skipping empty update of type ${type.slug}`);
130
+ }
131
+ await uploadReducedTypeProperties(updatedType, found.typeProperties, type.typeProperties, optionSets);
99
132
  }
100
133
  }
101
134
  catch (err) {
@@ -103,7 +136,7 @@ async function uploadReducedTypes(dir) {
103
136
  }
104
137
  }
105
138
  }
106
- async function uploadReducedTypeProperties(type, currentPropertyTypes, newPropertyTypes) {
139
+ async function uploadReducedTypeProperties(type, currentPropertyTypes, newPropertyTypes, optionSets) {
107
140
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
108
141
  const currentPropertyTypeBySlug = {};
109
142
  for (const currentPropertyType of currentPropertyTypes) {
@@ -113,9 +146,6 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
113
146
  for (const newPropertyType of newPropertyTypes) {
114
147
  newPropertyTypesBySlug[newPropertyType.slug] = newPropertyType;
115
148
  }
116
- const optionSets = await new sdk_1.Entities().get({
117
- entityName: 'type-property-option-set',
118
- });
119
149
  const optionSetIdBySlug = {};
120
150
  for (const optionSet of optionSets) {
121
151
  optionSetIdBySlug[optionSet.slug] = optionSet.id;
@@ -123,7 +153,7 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
123
153
  for (const newPropertyType of newPropertyTypes) {
124
154
  try {
125
155
  const found = currentPropertyTypeBySlug[newPropertyType.slug];
126
- if (!found && !newPropertyType.inherited) {
156
+ if (!found) {
127
157
  logger_1.default.info(`Creating Type-Property: ${newPropertyType.slug}`);
128
158
  await sdk_1.Request.request(`/types/${type.id}/type-properties`, {
129
159
  method: 'POST',
@@ -154,35 +184,68 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
154
184
  },
155
185
  });
156
186
  }
157
- else if (found &&
158
- !newPropertyType.inherited &&
159
- !typePropertiesEqual(found, newPropertyType, optionSetIdBySlug)) {
160
- logger_1.default.info(`Updating Type-Property: ${newPropertyType.slug}`);
161
- await new sdk_1.Entities().update({
162
- entityName: 'type-property',
163
- id: found.id,
164
- object: {
165
- isArchived: (_a = newPropertyType.isArchived) !== null && _a !== void 0 ? _a : null,
166
- propertyType: (_b = newPropertyType.propertyType) !== null && _b !== void 0 ? _b : null,
167
- slug: (_c = newPropertyType.slug) !== null && _c !== void 0 ? _c : null,
168
- label: (_d = newPropertyType.label) !== null && _d !== void 0 ? _d : null,
169
- editable: (_e = newPropertyType.editable) !== null && _e !== void 0 ? _e : null,
170
- searchable: (_f = newPropertyType.searchable) !== null && _f !== void 0 ? _f : null,
171
- core: (_g = newPropertyType.core) !== null && _g !== void 0 ? _g : null,
172
- typePropertyOptionSetId: (_h = optionSetIdBySlug[newPropertyType.typePropertyOptionSetSlug]) !== null && _h !== void 0 ? _h : null,
173
- formula: (_j = newPropertyType.formula) !== null && _j !== void 0 ? _j : null,
174
- referencedTypePath: (_k = newPropertyType.referencedTypePath) !== null && _k !== void 0 ? _k : null,
175
- carryOverBehavior: (_l = newPropertyType.carryOverBehavior) !== null && _l !== void 0 ? _l : null,
176
- carryOverDefault: (_m = newPropertyType.carryOverDefault) !== null && _m !== void 0 ? _m : null,
177
- referencedTypeRootSlug: (_o = newPropertyType.referencedTypeRootSlug) !== null && _o !== void 0 ? _o : null,
178
- defaultValue: (_p = newPropertyType.defaultValue) !== null && _p !== void 0 ? _p : null,
179
- propertyLevel: (_q = newPropertyType.propertyLevel) !== null && _q !== void 0 ? _q : null,
180
- formulaFunction: (_r = newPropertyType.formulaFunction) !== null && _r !== void 0 ? _r : null,
181
- validationFunction: (_s = newPropertyType.validationFunction) !== null && _s !== void 0 ? _s : null,
182
- numberFormat: (_t = newPropertyType.numberFormat) !== null && _t !== void 0 ? _t : null,
183
- sequenceSeedValue: (_u = newPropertyType.sequenceSeedValue) !== null && _u !== void 0 ? _u : null,
184
- },
185
- });
187
+ else if (found
188
+ // !newPropertyType.inherited &&
189
+ // !typePropertiesEqual(found, newPropertyType, optionSetIdBySlug)
190
+ ) {
191
+ const proposedTP = {
192
+ isArchived: (_a = newPropertyType.isArchived) !== null && _a !== void 0 ? _a : null,
193
+ propertyType: (_b = newPropertyType.propertyType) !== null && _b !== void 0 ? _b : null,
194
+ slug: (_c = newPropertyType.slug) !== null && _c !== void 0 ? _c : null,
195
+ label: (_d = newPropertyType.label) !== null && _d !== void 0 ? _d : null,
196
+ editable: (_e = newPropertyType.editable) !== null && _e !== void 0 ? _e : null,
197
+ searchable: (_f = newPropertyType.searchable) !== null && _f !== void 0 ? _f : null,
198
+ core: (_g = newPropertyType.core) !== null && _g !== void 0 ? _g : null,
199
+ typePropertyOptionSetId: (_h = optionSetIdBySlug[newPropertyType.typePropertyOptionSetSlug]) !== null && _h !== void 0 ? _h : null,
200
+ formula: (_j = newPropertyType.formula) !== null && _j !== void 0 ? _j : null,
201
+ referencedTypePath: (_k = newPropertyType.referencedTypePath) !== null && _k !== void 0 ? _k : null,
202
+ carryOverBehavior: (_l = newPropertyType.carryOverBehavior) !== null && _l !== void 0 ? _l : null,
203
+ carryOverDefault: (_m = newPropertyType.carryOverDefault) !== null && _m !== void 0 ? _m : null,
204
+ referencedTypeRootSlug: (_o = newPropertyType.referencedTypeRootSlug) !== null && _o !== void 0 ? _o : null,
205
+ defaultValue: (_p = newPropertyType.defaultValue) !== null && _p !== void 0 ? _p : null,
206
+ propertyLevel: (_q = newPropertyType.propertyLevel) !== null && _q !== void 0 ? _q : null,
207
+ formulaFunction: (_r = newPropertyType.formulaFunction) !== null && _r !== void 0 ? _r : null,
208
+ validationFunction: (_s = newPropertyType.validationFunction) !== null && _s !== void 0 ? _s : null,
209
+ numberFormat: (_t = newPropertyType.numberFormat) !== null && _t !== void 0 ? _t : null,
210
+ sequenceSeedValue: (_u = newPropertyType.sequenceSeedValue) !== null && _u !== void 0 ? _u : null,
211
+ };
212
+ // logger.debug(
213
+ // `found type property option set: ${newPropertyType.typePropertyOptionSetSlug}, ${proposedTP.typePropertyOptionSetId}, `,
214
+ // );
215
+ const differences = extractValueDifferences(found, proposedTP, [
216
+ 'id',
217
+ 'createdOn',
218
+ 'updatedOn',
219
+ 'createdById',
220
+ 'updatedById',
221
+ 'orgId',
222
+ 'typePropertyUserListId',
223
+ 'referencedTypeRole',
224
+ 'referencedTypeFilterProperties',
225
+ 'referencedTypeId',
226
+ 'description',
227
+ 'apiDetails',
228
+ 'notes',
229
+ 'isFederationProperty',
230
+ 'required',
231
+ 'optionsSet',
232
+ 'options',
233
+ 'inherited',
234
+ 'used',
235
+ 'typePropertyPolicyIds',
236
+ ]);
237
+ if (differences.updates.length || differences.adds.length || differences.deletions.length) {
238
+ logger_1.default.debug(`found differences: ${JSON.stringify(differences, null, 2)}`);
239
+ logger_1.default.debug(`Updating Type-Property: ${newPropertyType.slug}, ${found.id}, ${JSON.stringify(proposedTP, null, 2)}`);
240
+ await new sdk_1.Entities().update({
241
+ entityName: 'type-property',
242
+ id: found.id,
243
+ object: proposedTP,
244
+ });
245
+ }
246
+ else {
247
+ logger_1.default.debug(`Skipping empty update of type property ${newPropertyType.slug}`);
248
+ }
186
249
  }
187
250
  }
188
251
  catch (err) {
@@ -208,48 +271,33 @@ async function uploadReducedTypeProperties(type, currentPropertyTypes, newProper
208
271
  }
209
272
  }
210
273
  }
211
- function typePropertiesEqual(typeProperty1, typeProperty2, optionSetIdByIdentifier) {
212
- const reducedProperty1 = {
213
- isArchived: typeProperty1.isArchived,
214
- propertyType: typeProperty1.propertyType,
215
- slug: typeProperty1.slug,
216
- label: typeProperty1.label,
217
- editable: typeProperty1.editable,
218
- searchable: typeProperty1.searchable,
219
- core: typeProperty1.core,
220
- typePropertyOptionSetId: optionSetIdByIdentifier[typeProperty1.typePropertyOptionSetSlug],
221
- formula: typeProperty1.formula,
222
- referencedTypePath: typeProperty1.referencedTypePath,
223
- referencedTypeRootSlug: typeProperty1.referencedTypeRootSlug,
224
- defaultValue: typeProperty1.defaultValue,
225
- propertyLevel: typeProperty1.propertyLevel,
226
- carryOverBehavior: typeProperty1.carryOverBehavior,
227
- carryOverDefault: typeProperty1.carryOverDefault,
228
- formulaFunction: typeProperty1.formulaFunction,
229
- validationFunction: typeProperty1.validationFunction,
230
- numberFormat: typeProperty1.numberFormat,
231
- sequenceSeedValue: typeProperty1.sequenceSeedValue,
232
- };
233
- const reducedProperty2 = {
234
- isArchived: typeProperty2.isArchived,
235
- propertyType: typeProperty2.propertyType,
236
- slug: typeProperty2.slug,
237
- label: typeProperty2.label,
238
- editable: typeProperty2.editable,
239
- searchable: typeProperty2.searchable,
240
- core: typeProperty2.core,
241
- typePropertyOptionSetId: optionSetIdByIdentifier[typeProperty2.typePropertyOptionSetSlug],
242
- formula: typeProperty2.formula,
243
- referencedTypePath: typeProperty2.referencedTypePath,
244
- referencedTypeRootSlug: typeProperty2.referencedTypeRootSlug,
245
- defaultValue: typeProperty2.defaultValue,
246
- propertyLevel: typeProperty2.propertyLevel,
247
- carryOverBehavior: typeProperty2.carryOverBehavior,
248
- carryOverDefault: typeProperty2.carryOverDefault,
249
- formulaFunction: typeProperty2.formulaFunction,
250
- validationFunction: typeProperty2.validationFunction,
251
- numberFormat: typeProperty2.numberFormat,
252
- sequenceSeedValue: typeProperty2.sequenceSeedValue,
274
+ /**
275
+ * compares the keys between different objects and returns the differences
276
+ * captures in adds, updates, and deletions. Object 1 is considered the
277
+ * original, and object 2 is considered the "edit".
278
+ *
279
+ * An add is a property that exists in object2 but not in 1. An edit
280
+ * is a property that exists in both object 1 and 2, but with different values.
281
+ * A deletion is a property in object 1 but not in object 2.
282
+ * @param object1 any object
283
+ * @param object2 any other object
284
+ */
285
+ function extractValueDifferences(object1, object2, keysToIgnore) {
286
+ const updates = [];
287
+ const object1Keys = Object.keys(object1);
288
+ const object2keys = Object.keys(object2);
289
+ const adds = object2keys.filter((key) => !object1Keys.includes(key) && !keysToIgnore.includes(key));
290
+ const deletions = object1Keys.filter((key) => !object2keys.includes(key) && !keysToIgnore.includes(key));
291
+ const shared = object1Keys.filter((key) => object2keys.includes(key) && !keysToIgnore.includes(key));
292
+ for (const key of shared) {
293
+ if (!_.isEqual(object1[key], object2[key])) {
294
+ updates.push(key);
295
+ }
296
+ }
297
+ return {
298
+ adds,
299
+ updates,
300
+ deletions,
253
301
  };
254
- return _.isEqual(reducedProperty1, reducedProperty2);
255
302
  }
303
+ exports.extractValueDifferences = extractValueDifferences;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const type_uploader_1 = require("./type-uploader");
4
+ describe('extractValueDifferences', () => {
5
+ it('should return the correct value differences', () => {
6
+ const object1 = {
7
+ name: 'John',
8
+ age: 30,
9
+ city: 'New York',
10
+ };
11
+ const object2 = {
12
+ name: 'John',
13
+ age: 35,
14
+ city: 'Los Angeles',
15
+ country: 'USA',
16
+ };
17
+ const expectedValueDiff = {
18
+ adds: ['country'],
19
+ updates: ['age', 'city'],
20
+ deletions: [],
21
+ };
22
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, []);
23
+ expect(valueDiff).toEqual(expectedValueDiff);
24
+ });
25
+ it('should handle empty objects', () => {
26
+ const object1 = {};
27
+ const object2 = {};
28
+ const expectedValueDiff = {
29
+ adds: [],
30
+ updates: [],
31
+ deletions: [],
32
+ };
33
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, []);
34
+ expect(valueDiff).toEqual(expectedValueDiff);
35
+ });
36
+ it('should handle objects with no shared keys', () => {
37
+ const object1 = {
38
+ name: 'John',
39
+ age: 30,
40
+ };
41
+ const object2 = {
42
+ city: 'New York',
43
+ country: 'USA',
44
+ };
45
+ const expectedValueDiff = {
46
+ adds: ['city', 'country'],
47
+ updates: [],
48
+ deletions: ['name', 'age'],
49
+ };
50
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, []);
51
+ expect(valueDiff).toEqual(expectedValueDiff);
52
+ });
53
+ it('should handle objects with shared keys but no differences', () => {
54
+ const object1 = {
55
+ name: 'John',
56
+ age: 30,
57
+ };
58
+ const object2 = {
59
+ name: 'John',
60
+ age: 30,
61
+ };
62
+ const expectedValueDiff = {
63
+ adds: [],
64
+ updates: [],
65
+ deletions: [],
66
+ };
67
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, []);
68
+ expect(valueDiff).toEqual(expectedValueDiff);
69
+ });
70
+ it('should handle correctly ignore keys on deletion', () => {
71
+ const object1 = {
72
+ name: 'John',
73
+ age: 30,
74
+ };
75
+ const object2 = {
76
+ name: 'John',
77
+ };
78
+ const expectedValueDiff = {
79
+ adds: [],
80
+ updates: [],
81
+ deletions: [],
82
+ };
83
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, ['age']);
84
+ expect(valueDiff).toEqual(expectedValueDiff);
85
+ });
86
+ it('should handle correctly ignore keys on updates', () => {
87
+ const object1 = {
88
+ name: 'John',
89
+ age: 30,
90
+ };
91
+ const object2 = {
92
+ name: 'John',
93
+ age: 15,
94
+ };
95
+ const expectedValueDiff = {
96
+ adds: [],
97
+ updates: [],
98
+ deletions: [],
99
+ };
100
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, ['age']);
101
+ expect(valueDiff).toEqual(expectedValueDiff);
102
+ });
103
+ it('should handle correctly ignore keys on deletion', () => {
104
+ const object1 = {
105
+ name: 'John',
106
+ };
107
+ const object2 = {
108
+ name: 'John',
109
+ age: 30,
110
+ };
111
+ const expectedValueDiff = {
112
+ adds: [],
113
+ updates: [],
114
+ deletions: [],
115
+ };
116
+ const valueDiff = (0, type_uploader_1.extractValueDifferences)(object1, object2, ['age']);
117
+ expect(valueDiff).toEqual(expectedValueDiff);
118
+ });
119
+ });
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.6",
2
+ "version": "2.2.8",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "2.2.6",
3
+ "version": "2.2.8",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",