@extrahorizon/exh-cli 1.5.0 → 1.5.1-dev-27-7bdff2e
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/CHANGELOG.md +56 -53
- package/README.md +0 -12
- package/build/commands/completion.d.ts +5 -5
- package/build/commands/completion.js +38 -38
- package/build/commands/data/schemas/delete.d.ts +10 -10
- package/build/commands/data/schemas/delete.js +29 -29
- package/build/commands/data/schemas/list.d.ts +8 -8
- package/build/commands/data/schemas/list.js +20 -20
- package/build/commands/data/schemas/sync/statusHelpers.d.ts +3 -3
- package/build/commands/data/schemas/sync/statusHelpers.js +49 -49
- package/build/commands/data/schemas/sync.d.ts +35 -35
- package/build/commands/data/schemas/sync.js +72 -72
- package/build/commands/data/schemas/util/listFilesInDir.d.ts +1 -1
- package/build/commands/data/schemas/util/listFilesInDir.js +25 -25
- package/build/commands/data/schemas/util/metaschema.json +455 -455
- package/build/commands/data/schemas/util/readJson.d.ts +1 -1
- package/build/commands/data/schemas/util/readJson.js +11 -11
- package/build/commands/data/schemas/util/schemaverify.d.ts +28 -28
- package/build/commands/data/schemas/util/schemaverify.js +202 -202
- package/build/commands/data/schemas/util/syncSchema.d.ts +20 -20
- package/build/commands/data/schemas/util/syncSchema.js +276 -276
- package/build/commands/data/schemas/util/tests/listFilesInDir.test.d.ts +1 -1
- package/build/commands/data/schemas/util/tests/listFilesInDir.test.js +40 -40
- package/build/commands/data/schemas/verify.d.ts +18 -18
- package/build/commands/data/schemas/verify.js +82 -82
- package/build/commands/data/schemas.d.ts +5 -5
- package/build/commands/data/schemas.js +10 -10
- package/build/commands/data.d.ts +5 -5
- package/build/commands/data.js +10 -10
- package/build/commands/dispatchers/sync.d.ts +21 -21
- package/build/commands/dispatchers/sync.js +24 -24
- package/build/commands/dispatchers.d.ts +5 -5
- package/build/commands/dispatchers.js +10 -10
- package/build/commands/login.d.ts +37 -37
- package/build/commands/login.js +59 -59
- package/build/commands/sync.d.ts +55 -55
- package/build/commands/sync.js +107 -107
- package/build/commands/tasks/createrepo.d.ts +22 -22
- package/build/commands/tasks/createrepo.js +70 -70
- package/build/commands/tasks/delete.d.ts +10 -10
- package/build/commands/tasks/delete.js +29 -29
- package/build/commands/tasks/list.d.ts +8 -8
- package/build/commands/tasks/list.js +27 -27
- package/build/commands/tasks/sync.d.ts +56 -56
- package/build/commands/tasks/sync.js +122 -122
- package/build/commands/tasks/taskConfig.d.ts +24 -24
- package/build/commands/tasks/taskConfig.js +169 -169
- package/build/commands/tasks/util.d.ts +1 -1
- package/build/commands/tasks/util.js +27 -27
- package/build/commands/tasks.d.ts +5 -5
- package/build/commands/tasks.js +10 -10
- package/build/commands/templates/delete.d.ts +19 -19
- package/build/commands/templates/delete.js +48 -48
- package/build/commands/templates/get.d.ts +19 -19
- package/build/commands/templates/get.js +37 -37
- package/build/commands/templates/list.d.ts +9 -9
- package/build/commands/templates/list.js +25 -25
- package/build/commands/templates/sync.d.ts +22 -22
- package/build/commands/templates/sync.js +65 -65
- package/build/commands/templates/util/buildTemplates.d.ts +2 -2
- package/build/commands/templates/util/buildTemplates.js +50 -50
- package/build/commands/templates/util/readTemplateFiles.d.ts +3 -3
- package/build/commands/templates/util/readTemplateFiles.js +52 -52
- package/build/commands/templates/util/templateService.d.ts +8 -8
- package/build/commands/templates/util/templateService.js +18 -18
- package/build/commands/templates/util/uploadTemplate.d.ts +2 -2
- package/build/commands/templates/util/uploadTemplate.js +20 -20
- package/build/commands/templates/util/utils.d.ts +4 -4
- package/build/commands/templates/util/utils.js +22 -22
- package/build/commands/templates.d.ts +5 -5
- package/build/commands/templates.js +10 -10
- package/build/constants.d.ts +13 -13
- package/build/constants.js +37 -37
- package/build/exh.d.ts +3 -3
- package/build/exh.js +65 -65
- package/build/helpers/error.d.ts +2 -2
- package/build/helpers/error.js +6 -6
- package/build/helpers/repoConfig.d.ts +2 -2
- package/build/helpers/repoConfig.js +60 -60
- package/build/helpers/util.d.ts +3 -3
- package/build/helpers/util.js +36 -36
- package/build/index.d.ts +2 -2
- package/build/index.js +33 -33
- package/build/repositories/dispatchers.d.ts +8 -8
- package/build/repositories/dispatchers.js +31 -31
- package/build/repositories/functions.d.ts +24 -24
- package/build/repositories/functions.js +28 -28
- package/build/repositories/schemas.d.ts +44 -44
- package/build/repositories/schemas.js +86 -86
- package/build/services/dispatchers.d.ts +3 -3
- package/build/services/dispatchers.js +132 -132
- package/package.json +53 -53
|
@@ -1,276 +1,276 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6
|
-
};
|
|
7
|
-
var _SyncSchema_instances, _SyncSchema_syncRootAttributes, _SyncSchema_syncProperties, _SyncSchema_updateStatuses, _SyncSchema_syncCreationTransition, _SyncSchema_syncTransitions, _SyncSchema_pruneStatuses, _SyncSchema_syncIndexes;
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.compareSchemaKey = exports.SyncSchema = void 0;
|
|
10
|
-
const chalk = require("chalk");
|
|
11
|
-
const _ = require("lodash");
|
|
12
|
-
const schemaRepository = require("../../../../repositories/schemas");
|
|
13
|
-
const statusHelpers_1 = require("../sync/statusHelpers");
|
|
14
|
-
class SyncSchema {
|
|
15
|
-
constructor(sdk, dry) {
|
|
16
|
-
_SyncSchema_instances.add(this);
|
|
17
|
-
this.cloudSchema = null;
|
|
18
|
-
this.localSchema = null;
|
|
19
|
-
this.sdk = sdk;
|
|
20
|
-
this.dry = dry;
|
|
21
|
-
}
|
|
22
|
-
static createSchemaSync(sdk, dry) {
|
|
23
|
-
return new SyncSchema(sdk, dry);
|
|
24
|
-
}
|
|
25
|
-
async sync(target) {
|
|
26
|
-
this.localSchema = target;
|
|
27
|
-
if (!this.localSchema.name) {
|
|
28
|
-
console.log('No schema name defined, skipping this file');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
console.log(`Syncing ${this.localSchema.name}`);
|
|
32
|
-
this.cloudSchema = await schemaRepository.fetchSchemaByName(this.sdk, this.localSchema.name);
|
|
33
|
-
if (!this.cloudSchema) {
|
|
34
|
-
if (this.dry) {
|
|
35
|
-
console.log(`\t-> Will be created: ${chalk.green(this.localSchema.name)}`);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
this.cloudSchema = await schemaRepository.createSchema(this.sdk, this.localSchema.name, this.localSchema.description);
|
|
39
|
-
}
|
|
40
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncRootAttributes).call(this);
|
|
41
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncProperties).call(this);
|
|
42
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_updateStatuses).call(this);
|
|
43
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncCreationTransition).call(this);
|
|
44
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncTransitions).call(this);
|
|
45
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_pruneStatuses).call(this);
|
|
46
|
-
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncIndexes).call(this);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
exports.SyncSchema = SyncSchema;
|
|
50
|
-
_SyncSchema_instances = new WeakSet(), _SyncSchema_syncRootAttributes = async function _SyncSchema_syncRootAttributes() {
|
|
51
|
-
const diff = diffRootAttributes(this.localSchema, this.cloudSchema);
|
|
52
|
-
if (this.dry) {
|
|
53
|
-
reportRootAttributesChanges(this.cloudSchema, diff);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (Object.keys(diff).length > 0) {
|
|
57
|
-
await schemaRepository.updateSchema(this.sdk, this.cloudSchema.id, diff);
|
|
58
|
-
}
|
|
59
|
-
}, _SyncSchema_syncProperties = async function _SyncSchema_syncProperties() {
|
|
60
|
-
console.log(JSON.stringify({ local: this.localSchema, cloud: this.cloudSchema }, null, 2));
|
|
61
|
-
const propertiesDiff = compareSchemaKey(this.localSchema, this.cloudSchema, 'properties');
|
|
62
|
-
if (this.dry) {
|
|
63
|
-
reportSchemaChanges(`Schema ${this.cloudSchema.name} - Properties`, propertiesDiff);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const { toAdd, toRemove, toUpdate } = propertiesDiff;
|
|
67
|
-
for (const key of toAdd) {
|
|
68
|
-
console.log(`properties: adding ${key}`);
|
|
69
|
-
await schemaRepository.createProperty(this.sdk, this.cloudSchema.id, {
|
|
70
|
-
name: key,
|
|
71
|
-
configuration: this.localSchema.properties[key],
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
for (const key of toUpdate) {
|
|
75
|
-
console.log(`properties: updating ${key}`);
|
|
76
|
-
await schemaRepository.updateProperty(this.sdk, this.cloudSchema.id, key, this.localSchema.properties[key]);
|
|
77
|
-
}
|
|
78
|
-
for (const key of toRemove) {
|
|
79
|
-
console.log(`properties: removing ${key}`);
|
|
80
|
-
await schemaRepository.deleteProperty(this.sdk, this.cloudSchema.id, key);
|
|
81
|
-
}
|
|
82
|
-
}, _SyncSchema_updateStatuses = async function _SyncSchema_updateStatuses() {
|
|
83
|
-
const changes = (0, statusHelpers_1.compareStatuses)(this.localSchema, this.cloudSchema);
|
|
84
|
-
if (this.dry) {
|
|
85
|
-
reportSchemaChanges(`Schema ${this.cloudSchema.name} - Statuses`, changes);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const { toAdd, toUpdate } = changes;
|
|
89
|
-
for (const key of toAdd) {
|
|
90
|
-
console.log(`statuses: adding ${key}`);
|
|
91
|
-
await schemaRepository.createStatus(this.sdk, this.cloudSchema.id, key, this.localSchema.statuses[key]);
|
|
92
|
-
}
|
|
93
|
-
for (const key of toUpdate) {
|
|
94
|
-
console.log(`statuses: updating ${key}`);
|
|
95
|
-
const data = (0, statusHelpers_1.calculateStatusUpdateData)(this.localSchema.statuses[key], this.cloudSchema.statuses[key]);
|
|
96
|
-
await schemaRepository.updateStatus(this.sdk, this.cloudSchema.id, key, data);
|
|
97
|
-
}
|
|
98
|
-
}, _SyncSchema_syncCreationTransition = async function _SyncSchema_syncCreationTransition() {
|
|
99
|
-
if (!this.localSchema.creationTransition) {
|
|
100
|
-
console.log(`Skipping creationTransition: No creationTransition defined in local ${this.localSchema.name} schema`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (!_.isEqual(this.cloudSchema.creationTransition, this.localSchema.creationTransition)) {
|
|
104
|
-
if (this.dry) {
|
|
105
|
-
console.log('Update creation transition');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
console.log('creation transition: updating');
|
|
109
|
-
await schemaRepository.updateCreationTransition(this.sdk, this.cloudSchema.id, this.localSchema.creationTransition);
|
|
110
|
-
}
|
|
111
|
-
}, _SyncSchema_syncTransitions = async function _SyncSchema_syncTransitions() {
|
|
112
|
-
const transitionsDiff = compareSchemaKey(this.localSchema, this.cloudSchema, 'transitions');
|
|
113
|
-
if (this.dry) {
|
|
114
|
-
reportSchemaChanges(`Schema ${this.cloudSchema.name} - Transitions`, transitionsDiff);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const { toAdd, toRemove, toUpdate } = transitionsDiff;
|
|
118
|
-
for (const transition of toAdd) {
|
|
119
|
-
console.log(`transitions: adding ${transition.name}`);
|
|
120
|
-
await schemaRepository.createTransition(this.sdk, this.cloudSchema.id, transition);
|
|
121
|
-
}
|
|
122
|
-
for (const transition of toUpdate) {
|
|
123
|
-
console.log(`transitions: updating ${transition.name}`);
|
|
124
|
-
const currentTransition = findTransitionByName(transition.name, this.cloudSchema.transitions);
|
|
125
|
-
await schemaRepository.updateTransition(this.sdk, this.cloudSchema.id, currentTransition.id, transition);
|
|
126
|
-
}
|
|
127
|
-
for (const transition of toRemove) {
|
|
128
|
-
console.log(`transitions: removing ${transition.name}`);
|
|
129
|
-
await schemaRepository.deleteTransition(this.sdk, this.cloudSchema.id, transition.id);
|
|
130
|
-
}
|
|
131
|
-
}, _SyncSchema_pruneStatuses = async function _SyncSchema_pruneStatuses() {
|
|
132
|
-
if (this.dry) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const excessStatuses = _.difference(Object.keys(this.cloudSchema.statuses), Object.keys(this.localSchema.statuses));
|
|
136
|
-
for (const key of excessStatuses) {
|
|
137
|
-
console.log(`statuses: removing ${key}`);
|
|
138
|
-
await schemaRepository.deleteStatus(this.sdk, this.cloudSchema.id, key);
|
|
139
|
-
}
|
|
140
|
-
}, _SyncSchema_syncIndexes = async function _SyncSchema_syncIndexes() {
|
|
141
|
-
const { newIndexes, removedIndexes } = compareIndexes(this.localSchema, this.cloudSchema);
|
|
142
|
-
if (this.dry) {
|
|
143
|
-
reportIndexChanges(this.localSchema, { newIndexes, removedIndexes });
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
for (const idx of removedIndexes) {
|
|
147
|
-
console.log(`Indexes: remove index ${idx.id}`);
|
|
148
|
-
await schemaRepository.deleteIndex(this.sdk, this.cloudSchema.id, idx.id);
|
|
149
|
-
}
|
|
150
|
-
for (const idx of newIndexes) {
|
|
151
|
-
console.log('\t-> Creating new index');
|
|
152
|
-
await schemaRepository.createIndex(this.sdk, this.cloudSchema.id, idx);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
function deepDiff(object, other) {
|
|
156
|
-
return _.transform(object, (result, value, key) => {
|
|
157
|
-
if (!_.isEqual(value, other[key])) {
|
|
158
|
-
result[key] = _.isObject(value) && _.isObject(other[key]) ? deepDiff(value, other[key]) : value;
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function diffRootAttributes(localSchema, cloudSchema) {
|
|
163
|
-
return deepDiff(_.pick(localSchema, 'description', 'defaultLimit', 'maximumLimit', 'createMode', 'readMode', 'updateMode', 'deleteMode', 'groupSyncMode'), _.pick(cloudSchema, 'description', 'defaultLimit', 'maximumLimit', 'createMode', 'readMode', 'updateMode', 'deleteMode', 'groupSyncMode'));
|
|
164
|
-
}
|
|
165
|
-
function reportRootAttributesChanges(cloudSchema, updatedValues) {
|
|
166
|
-
const changedKeys = Object.keys(updatedValues);
|
|
167
|
-
console.group(`Schema ${cloudSchema.name} - Root attributes`);
|
|
168
|
-
if (changedKeys.length < 1) {
|
|
169
|
-
console.log('No update required.');
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
changedKeys.forEach(key => {
|
|
173
|
-
console.log(`${chalk.yellow(key)}:\t ${chalk.red(cloudSchema[key])} => ${chalk.green(updatedValues[key])}`);
|
|
174
|
-
});
|
|
175
|
-
console.groupEnd();
|
|
176
|
-
}
|
|
177
|
-
function reportSchemaChanges(group, changes) {
|
|
178
|
-
const { toAdd, toRemove, toUpdate } = changes;
|
|
179
|
-
console.group(group);
|
|
180
|
-
if (!toAdd.length && !toRemove.length && !toUpdate.length) {
|
|
181
|
-
console.log('No update required');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
toAdd.forEach(key => console.log(`Will be added: ${chalk.green(getIdentifier(key))}`));
|
|
185
|
-
toRemove.forEach(key => console.log(`Will be removed: ${chalk.red(getIdentifier(key))}`));
|
|
186
|
-
toUpdate.forEach(key => console.log(`Will be updated: ${chalk.yellow(getIdentifier(key))}`));
|
|
187
|
-
console.groupEnd();
|
|
188
|
-
}
|
|
189
|
-
function reportIndexChanges(schema, indexChanges) {
|
|
190
|
-
const { newIndexes, removedIndexes } = indexChanges;
|
|
191
|
-
const changes = compareArraysByName(newIndexes, removedIndexes);
|
|
192
|
-
reportSchemaChanges(`Schema ${schema.name} - Indexes`, {
|
|
193
|
-
toAdd: changes.toAdd.map(v => v.name),
|
|
194
|
-
toRemove: changes.toRemove.map(v => v.name),
|
|
195
|
-
toUpdate: changes.toUpdate.map(v => v.name),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
function compareSchemaKey(localSchema, cloudSchema, key) {
|
|
199
|
-
if (!localSchema[key] && !cloudSchema[key]) {
|
|
200
|
-
return {
|
|
201
|
-
toAdd: [],
|
|
202
|
-
toRemove: [],
|
|
203
|
-
toUpdate: [],
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
const isLocalPropertyArray = Array.isArray(localSchema[key]);
|
|
207
|
-
const isCloudPropertyArray = !localSchema[key] && Array.isArray(cloudSchema[key]);
|
|
208
|
-
if (isLocalPropertyArray || isCloudPropertyArray) {
|
|
209
|
-
return compareArraysByName(localSchema[key], cloudSchema[key]);
|
|
210
|
-
}
|
|
211
|
-
return compareSchemas(localSchema[key], cloudSchema[key]);
|
|
212
|
-
}
|
|
213
|
-
exports.compareSchemaKey = compareSchemaKey;
|
|
214
|
-
function compareSchemas(localSchema, cloudSchema) {
|
|
215
|
-
const toAdd = _.difference(Object.keys(localSchema), Object.keys(cloudSchema));
|
|
216
|
-
const toRemove = _.difference(Object.keys(cloudSchema), Object.keys(localSchema));
|
|
217
|
-
const existingLocalProperties = _(localSchema).omit(toAdd).value();
|
|
218
|
-
const existingCloudProperties = _(cloudSchema).omit(toRemove).value();
|
|
219
|
-
const schemaDiff = deepDiff(existingLocalProperties, existingCloudProperties);
|
|
220
|
-
const toUpdate = Object.keys(schemaDiff);
|
|
221
|
-
return {
|
|
222
|
-
toAdd,
|
|
223
|
-
toRemove,
|
|
224
|
-
toUpdate,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function compareArraysByName(localSchema, cloudSchema) {
|
|
228
|
-
const toAdd = _.differenceBy(localSchema, cloudSchema, 'name');
|
|
229
|
-
const toRemove = _.differenceBy(cloudSchema, localSchema, 'name');
|
|
230
|
-
const existingLocalProperties = _.differenceBy(localSchema, toAdd, 'name');
|
|
231
|
-
const existingCloudProperties = _.differenceBy(cloudSchema, toRemove, 'name');
|
|
232
|
-
const toUpdate = _.differenceWith(existingLocalProperties, existingCloudProperties, (targetSchema, currentSchema) => _.isEqual(_.omit(targetSchema, ['id']), _.omit(currentSchema, ['id'])));
|
|
233
|
-
return {
|
|
234
|
-
toAdd,
|
|
235
|
-
toRemove,
|
|
236
|
-
toUpdate,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
function findTransitionByName(name, transitions) {
|
|
240
|
-
return _.find(transitions, transition => transition.name === name);
|
|
241
|
-
}
|
|
242
|
-
function compareIndexes(localSchema, cloudSchema) {
|
|
243
|
-
const localIndexes = localSchema?.indexes ?? [];
|
|
244
|
-
const indexesManagedByUser = cloudSchema.indexes
|
|
245
|
-
.filter((index) => index.system === false)
|
|
246
|
-
.map((idx) => ({ idx, marked: false }));
|
|
247
|
-
const newIndexes = [];
|
|
248
|
-
for (const localIndex of localIndexes) {
|
|
249
|
-
let existsInCloud = false;
|
|
250
|
-
for (const cloudIndex of indexesManagedByUser) {
|
|
251
|
-
const cloudIndexContents = _.omit(cloudIndex.idx, ['name', 'id', 'system']);
|
|
252
|
-
const localIndexContents = _.omit({ options: {}, ...localIndex }, ['name']);
|
|
253
|
-
if (_.isEqual(cloudIndexContents, localIndexContents)) {
|
|
254
|
-
existsInCloud = true;
|
|
255
|
-
cloudIndex.marked = true;
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
if (!existsInCloud) {
|
|
260
|
-
newIndexes.push(localIndex);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const removedIndexes = indexesManagedByUser
|
|
264
|
-
.filter((index) => index.marked === false)
|
|
265
|
-
.map((index) => index.idx);
|
|
266
|
-
return { removedIndexes, newIndexes };
|
|
267
|
-
}
|
|
268
|
-
function getIdentifier(value) {
|
|
269
|
-
if (typeof value !== 'object') {
|
|
270
|
-
return value;
|
|
271
|
-
}
|
|
272
|
-
if (value.id) {
|
|
273
|
-
return value.id;
|
|
274
|
-
}
|
|
275
|
-
return value.name;
|
|
276
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6
|
+
};
|
|
7
|
+
var _SyncSchema_instances, _SyncSchema_syncRootAttributes, _SyncSchema_syncProperties, _SyncSchema_updateStatuses, _SyncSchema_syncCreationTransition, _SyncSchema_syncTransitions, _SyncSchema_pruneStatuses, _SyncSchema_syncIndexes;
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.compareSchemaKey = exports.SyncSchema = void 0;
|
|
10
|
+
const chalk = require("chalk");
|
|
11
|
+
const _ = require("lodash");
|
|
12
|
+
const schemaRepository = require("../../../../repositories/schemas");
|
|
13
|
+
const statusHelpers_1 = require("../sync/statusHelpers");
|
|
14
|
+
class SyncSchema {
|
|
15
|
+
constructor(sdk, dry) {
|
|
16
|
+
_SyncSchema_instances.add(this);
|
|
17
|
+
this.cloudSchema = null;
|
|
18
|
+
this.localSchema = null;
|
|
19
|
+
this.sdk = sdk;
|
|
20
|
+
this.dry = dry;
|
|
21
|
+
}
|
|
22
|
+
static createSchemaSync(sdk, dry) {
|
|
23
|
+
return new SyncSchema(sdk, dry);
|
|
24
|
+
}
|
|
25
|
+
async sync(target) {
|
|
26
|
+
this.localSchema = target;
|
|
27
|
+
if (!this.localSchema.name) {
|
|
28
|
+
console.log('No schema name defined, skipping this file');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(`Syncing ${this.localSchema.name}`);
|
|
32
|
+
this.cloudSchema = await schemaRepository.fetchSchemaByName(this.sdk, this.localSchema.name);
|
|
33
|
+
if (!this.cloudSchema) {
|
|
34
|
+
if (this.dry) {
|
|
35
|
+
console.log(`\t-> Will be created: ${chalk.green(this.localSchema.name)}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.cloudSchema = await schemaRepository.createSchema(this.sdk, this.localSchema.name, this.localSchema.description);
|
|
39
|
+
}
|
|
40
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncRootAttributes).call(this);
|
|
41
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncProperties).call(this);
|
|
42
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_updateStatuses).call(this);
|
|
43
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncCreationTransition).call(this);
|
|
44
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncTransitions).call(this);
|
|
45
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_pruneStatuses).call(this);
|
|
46
|
+
await __classPrivateFieldGet(this, _SyncSchema_instances, "m", _SyncSchema_syncIndexes).call(this);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.SyncSchema = SyncSchema;
|
|
50
|
+
_SyncSchema_instances = new WeakSet(), _SyncSchema_syncRootAttributes = async function _SyncSchema_syncRootAttributes() {
|
|
51
|
+
const diff = diffRootAttributes(this.localSchema, this.cloudSchema);
|
|
52
|
+
if (this.dry) {
|
|
53
|
+
reportRootAttributesChanges(this.cloudSchema, diff);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (Object.keys(diff).length > 0) {
|
|
57
|
+
await schemaRepository.updateSchema(this.sdk, this.cloudSchema.id, diff);
|
|
58
|
+
}
|
|
59
|
+
}, _SyncSchema_syncProperties = async function _SyncSchema_syncProperties() {
|
|
60
|
+
console.log(JSON.stringify({ local: this.localSchema, cloud: this.cloudSchema }, null, 2));
|
|
61
|
+
const propertiesDiff = compareSchemaKey(this.localSchema, this.cloudSchema, 'properties');
|
|
62
|
+
if (this.dry) {
|
|
63
|
+
reportSchemaChanges(`Schema ${this.cloudSchema.name} - Properties`, propertiesDiff);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const { toAdd, toRemove, toUpdate } = propertiesDiff;
|
|
67
|
+
for (const key of toAdd) {
|
|
68
|
+
console.log(`properties: adding ${key}`);
|
|
69
|
+
await schemaRepository.createProperty(this.sdk, this.cloudSchema.id, {
|
|
70
|
+
name: key,
|
|
71
|
+
configuration: this.localSchema.properties[key],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
for (const key of toUpdate) {
|
|
75
|
+
console.log(`properties: updating ${key}`);
|
|
76
|
+
await schemaRepository.updateProperty(this.sdk, this.cloudSchema.id, key, this.localSchema.properties[key]);
|
|
77
|
+
}
|
|
78
|
+
for (const key of toRemove) {
|
|
79
|
+
console.log(`properties: removing ${key}`);
|
|
80
|
+
await schemaRepository.deleteProperty(this.sdk, this.cloudSchema.id, key);
|
|
81
|
+
}
|
|
82
|
+
}, _SyncSchema_updateStatuses = async function _SyncSchema_updateStatuses() {
|
|
83
|
+
const changes = (0, statusHelpers_1.compareStatuses)(this.localSchema, this.cloudSchema);
|
|
84
|
+
if (this.dry) {
|
|
85
|
+
reportSchemaChanges(`Schema ${this.cloudSchema.name} - Statuses`, changes);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { toAdd, toUpdate } = changes;
|
|
89
|
+
for (const key of toAdd) {
|
|
90
|
+
console.log(`statuses: adding ${key}`);
|
|
91
|
+
await schemaRepository.createStatus(this.sdk, this.cloudSchema.id, key, this.localSchema.statuses[key]);
|
|
92
|
+
}
|
|
93
|
+
for (const key of toUpdate) {
|
|
94
|
+
console.log(`statuses: updating ${key}`);
|
|
95
|
+
const data = (0, statusHelpers_1.calculateStatusUpdateData)(this.localSchema.statuses[key], this.cloudSchema.statuses[key]);
|
|
96
|
+
await schemaRepository.updateStatus(this.sdk, this.cloudSchema.id, key, data);
|
|
97
|
+
}
|
|
98
|
+
}, _SyncSchema_syncCreationTransition = async function _SyncSchema_syncCreationTransition() {
|
|
99
|
+
if (!this.localSchema.creationTransition) {
|
|
100
|
+
console.log(`Skipping creationTransition: No creationTransition defined in local ${this.localSchema.name} schema`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!_.isEqual(this.cloudSchema.creationTransition, this.localSchema.creationTransition)) {
|
|
104
|
+
if (this.dry) {
|
|
105
|
+
console.log('Update creation transition');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log('creation transition: updating');
|
|
109
|
+
await schemaRepository.updateCreationTransition(this.sdk, this.cloudSchema.id, this.localSchema.creationTransition);
|
|
110
|
+
}
|
|
111
|
+
}, _SyncSchema_syncTransitions = async function _SyncSchema_syncTransitions() {
|
|
112
|
+
const transitionsDiff = compareSchemaKey(this.localSchema, this.cloudSchema, 'transitions');
|
|
113
|
+
if (this.dry) {
|
|
114
|
+
reportSchemaChanges(`Schema ${this.cloudSchema.name} - Transitions`, transitionsDiff);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const { toAdd, toRemove, toUpdate } = transitionsDiff;
|
|
118
|
+
for (const transition of toAdd) {
|
|
119
|
+
console.log(`transitions: adding ${transition.name}`);
|
|
120
|
+
await schemaRepository.createTransition(this.sdk, this.cloudSchema.id, transition);
|
|
121
|
+
}
|
|
122
|
+
for (const transition of toUpdate) {
|
|
123
|
+
console.log(`transitions: updating ${transition.name}`);
|
|
124
|
+
const currentTransition = findTransitionByName(transition.name, this.cloudSchema.transitions);
|
|
125
|
+
await schemaRepository.updateTransition(this.sdk, this.cloudSchema.id, currentTransition.id, transition);
|
|
126
|
+
}
|
|
127
|
+
for (const transition of toRemove) {
|
|
128
|
+
console.log(`transitions: removing ${transition.name}`);
|
|
129
|
+
await schemaRepository.deleteTransition(this.sdk, this.cloudSchema.id, transition.id);
|
|
130
|
+
}
|
|
131
|
+
}, _SyncSchema_pruneStatuses = async function _SyncSchema_pruneStatuses() {
|
|
132
|
+
if (this.dry) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const excessStatuses = _.difference(Object.keys(this.cloudSchema.statuses), Object.keys(this.localSchema.statuses));
|
|
136
|
+
for (const key of excessStatuses) {
|
|
137
|
+
console.log(`statuses: removing ${key}`);
|
|
138
|
+
await schemaRepository.deleteStatus(this.sdk, this.cloudSchema.id, key);
|
|
139
|
+
}
|
|
140
|
+
}, _SyncSchema_syncIndexes = async function _SyncSchema_syncIndexes() {
|
|
141
|
+
const { newIndexes, removedIndexes } = compareIndexes(this.localSchema, this.cloudSchema);
|
|
142
|
+
if (this.dry) {
|
|
143
|
+
reportIndexChanges(this.localSchema, { newIndexes, removedIndexes });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
for (const idx of removedIndexes) {
|
|
147
|
+
console.log(`Indexes: remove index ${idx.id}`);
|
|
148
|
+
await schemaRepository.deleteIndex(this.sdk, this.cloudSchema.id, idx.id);
|
|
149
|
+
}
|
|
150
|
+
for (const idx of newIndexes) {
|
|
151
|
+
console.log('\t-> Creating new index');
|
|
152
|
+
await schemaRepository.createIndex(this.sdk, this.cloudSchema.id, idx);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
function deepDiff(object, other) {
|
|
156
|
+
return _.transform(object, (result, value, key) => {
|
|
157
|
+
if (!_.isEqual(value, other[key])) {
|
|
158
|
+
result[key] = _.isObject(value) && _.isObject(other[key]) ? deepDiff(value, other[key]) : value;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function diffRootAttributes(localSchema, cloudSchema) {
|
|
163
|
+
return deepDiff(_.pick(localSchema, 'description', 'defaultLimit', 'maximumLimit', 'createMode', 'readMode', 'updateMode', 'deleteMode', 'groupSyncMode'), _.pick(cloudSchema, 'description', 'defaultLimit', 'maximumLimit', 'createMode', 'readMode', 'updateMode', 'deleteMode', 'groupSyncMode'));
|
|
164
|
+
}
|
|
165
|
+
function reportRootAttributesChanges(cloudSchema, updatedValues) {
|
|
166
|
+
const changedKeys = Object.keys(updatedValues);
|
|
167
|
+
console.group(`Schema ${cloudSchema.name} - Root attributes`);
|
|
168
|
+
if (changedKeys.length < 1) {
|
|
169
|
+
console.log('No update required.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
changedKeys.forEach(key => {
|
|
173
|
+
console.log(`${chalk.yellow(key)}:\t ${chalk.red(cloudSchema[key])} => ${chalk.green(updatedValues[key])}`);
|
|
174
|
+
});
|
|
175
|
+
console.groupEnd();
|
|
176
|
+
}
|
|
177
|
+
function reportSchemaChanges(group, changes) {
|
|
178
|
+
const { toAdd, toRemove, toUpdate } = changes;
|
|
179
|
+
console.group(group);
|
|
180
|
+
if (!toAdd.length && !toRemove.length && !toUpdate.length) {
|
|
181
|
+
console.log('No update required');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
toAdd.forEach(key => console.log(`Will be added: ${chalk.green(getIdentifier(key))}`));
|
|
185
|
+
toRemove.forEach(key => console.log(`Will be removed: ${chalk.red(getIdentifier(key))}`));
|
|
186
|
+
toUpdate.forEach(key => console.log(`Will be updated: ${chalk.yellow(getIdentifier(key))}`));
|
|
187
|
+
console.groupEnd();
|
|
188
|
+
}
|
|
189
|
+
function reportIndexChanges(schema, indexChanges) {
|
|
190
|
+
const { newIndexes, removedIndexes } = indexChanges;
|
|
191
|
+
const changes = compareArraysByName(newIndexes, removedIndexes);
|
|
192
|
+
reportSchemaChanges(`Schema ${schema.name} - Indexes`, {
|
|
193
|
+
toAdd: changes.toAdd.map(v => v.name),
|
|
194
|
+
toRemove: changes.toRemove.map(v => v.name),
|
|
195
|
+
toUpdate: changes.toUpdate.map(v => v.name),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function compareSchemaKey(localSchema, cloudSchema, key) {
|
|
199
|
+
if (!localSchema[key] && !cloudSchema[key]) {
|
|
200
|
+
return {
|
|
201
|
+
toAdd: [],
|
|
202
|
+
toRemove: [],
|
|
203
|
+
toUpdate: [],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const isLocalPropertyArray = Array.isArray(localSchema[key]);
|
|
207
|
+
const isCloudPropertyArray = !localSchema[key] && Array.isArray(cloudSchema[key]);
|
|
208
|
+
if (isLocalPropertyArray || isCloudPropertyArray) {
|
|
209
|
+
return compareArraysByName(localSchema[key], cloudSchema[key]);
|
|
210
|
+
}
|
|
211
|
+
return compareSchemas(localSchema[key], cloudSchema[key]);
|
|
212
|
+
}
|
|
213
|
+
exports.compareSchemaKey = compareSchemaKey;
|
|
214
|
+
function compareSchemas(localSchema, cloudSchema) {
|
|
215
|
+
const toAdd = _.difference(Object.keys(localSchema), Object.keys(cloudSchema));
|
|
216
|
+
const toRemove = _.difference(Object.keys(cloudSchema), Object.keys(localSchema));
|
|
217
|
+
const existingLocalProperties = _(localSchema).omit(toAdd).value();
|
|
218
|
+
const existingCloudProperties = _(cloudSchema).omit(toRemove).value();
|
|
219
|
+
const schemaDiff = deepDiff(existingLocalProperties, existingCloudProperties);
|
|
220
|
+
const toUpdate = Object.keys(schemaDiff);
|
|
221
|
+
return {
|
|
222
|
+
toAdd,
|
|
223
|
+
toRemove,
|
|
224
|
+
toUpdate,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function compareArraysByName(localSchema, cloudSchema) {
|
|
228
|
+
const toAdd = _.differenceBy(localSchema, cloudSchema, 'name');
|
|
229
|
+
const toRemove = _.differenceBy(cloudSchema, localSchema, 'name');
|
|
230
|
+
const existingLocalProperties = _.differenceBy(localSchema, toAdd, 'name');
|
|
231
|
+
const existingCloudProperties = _.differenceBy(cloudSchema, toRemove, 'name');
|
|
232
|
+
const toUpdate = _.differenceWith(existingLocalProperties, existingCloudProperties, (targetSchema, currentSchema) => _.isEqual(_.omit(targetSchema, ['id']), _.omit(currentSchema, ['id'])));
|
|
233
|
+
return {
|
|
234
|
+
toAdd,
|
|
235
|
+
toRemove,
|
|
236
|
+
toUpdate,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function findTransitionByName(name, transitions) {
|
|
240
|
+
return _.find(transitions, transition => transition.name === name);
|
|
241
|
+
}
|
|
242
|
+
function compareIndexes(localSchema, cloudSchema) {
|
|
243
|
+
const localIndexes = localSchema?.indexes ?? [];
|
|
244
|
+
const indexesManagedByUser = cloudSchema.indexes
|
|
245
|
+
.filter((index) => index.system === false)
|
|
246
|
+
.map((idx) => ({ idx, marked: false }));
|
|
247
|
+
const newIndexes = [];
|
|
248
|
+
for (const localIndex of localIndexes) {
|
|
249
|
+
let existsInCloud = false;
|
|
250
|
+
for (const cloudIndex of indexesManagedByUser) {
|
|
251
|
+
const cloudIndexContents = _.omit(cloudIndex.idx, ['name', 'id', 'system']);
|
|
252
|
+
const localIndexContents = _.omit({ options: {}, ...localIndex }, ['name']);
|
|
253
|
+
if (_.isEqual(cloudIndexContents, localIndexContents)) {
|
|
254
|
+
existsInCloud = true;
|
|
255
|
+
cloudIndex.marked = true;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!existsInCloud) {
|
|
260
|
+
newIndexes.push(localIndex);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const removedIndexes = indexesManagedByUser
|
|
264
|
+
.filter((index) => index.marked === false)
|
|
265
|
+
.map((index) => index.idx);
|
|
266
|
+
return { removedIndexes, newIndexes };
|
|
267
|
+
}
|
|
268
|
+
function getIdentifier(value) {
|
|
269
|
+
if (typeof value !== 'object') {
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
if (value.id) {
|
|
273
|
+
return value.id;
|
|
274
|
+
}
|
|
275
|
+
return value.name;
|
|
276
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export {};
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const mockFs = require("mock-fs");
|
|
5
|
-
const listFilesInDir_1 = require("../listFilesInDir");
|
|
6
|
-
describe('listFilesInDir', () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockFs({
|
|
9
|
-
'path/to/fake/dir': {
|
|
10
|
-
'some-file.txt': 'file content here',
|
|
11
|
-
'first-json-file.json': 'json file here',
|
|
12
|
-
'a-directory': {
|
|
13
|
-
'some-file.txt': 'file content here',
|
|
14
|
-
'second-json-file.json': 'json file here',
|
|
15
|
-
},
|
|
16
|
-
'empty-directory': {},
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
mockFs.restore();
|
|
22
|
-
});
|
|
23
|
-
it('Returns a flat array of only json files', () => {
|
|
24
|
-
expect((0, listFilesInDir_1.flatListFiles)('path/to/fake/dir', '.json')).toStrictEqual([
|
|
25
|
-
path.join('path/to/fake/dir/a-directory/second-json-file.json'),
|
|
26
|
-
path.join('path/to/fake/dir/first-json-file.json'),
|
|
27
|
-
]);
|
|
28
|
-
});
|
|
29
|
-
it('Returns a flat array of all files', () => {
|
|
30
|
-
expect((0, listFilesInDir_1.flatListFiles)('path/to/fake/dir')).toStrictEqual([
|
|
31
|
-
path.join('path/to/fake/dir/a-directory/second-json-file.json'),
|
|
32
|
-
path.join('path/to/fake/dir/a-directory/some-file.txt'),
|
|
33
|
-
path.join('path/to/fake/dir/first-json-file.json'),
|
|
34
|
-
path.join('path/to/fake/dir/some-file.txt'),
|
|
35
|
-
]);
|
|
36
|
-
});
|
|
37
|
-
it('Returns an empty flat array of jpg files', () => {
|
|
38
|
-
expect((0, listFilesInDir_1.flatListFiles)('path/to/fake/dir', '.jpg')).toStrictEqual([]);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const mockFs = require("mock-fs");
|
|
5
|
+
const listFilesInDir_1 = require("../listFilesInDir");
|
|
6
|
+
describe('listFilesInDir', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
mockFs({
|
|
9
|
+
'path/to/fake/dir': {
|
|
10
|
+
'some-file.txt': 'file content here',
|
|
11
|
+
'first-json-file.json': 'json file here',
|
|
12
|
+
'a-directory': {
|
|
13
|
+
'some-file.txt': 'file content here',
|
|
14
|
+
'second-json-file.json': 'json file here',
|
|
15
|
+
},
|
|
16
|
+
'empty-directory': {},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
mockFs.restore();
|
|
22
|
+
});
|
|
23
|
+
it('Returns a flat array of only json files', () => {
|
|
24
|
+
expect((0, listFilesInDir_1.flatListFiles)('path/to/fake/dir', '.json')).toStrictEqual([
|
|
25
|
+
path.join('path/to/fake/dir/a-directory/second-json-file.json'),
|
|
26
|
+
path.join('path/to/fake/dir/first-json-file.json'),
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
it('Returns a flat array of all files', () => {
|
|
30
|
+
expect((0, listFilesInDir_1.flatListFiles)('path/to/fake/dir')).toStrictEqual([
|
|
31
|
+
path.join('path/to/fake/dir/a-directory/second-json-file.json'),
|
|
32
|
+
path.join('path/to/fake/dir/a-directory/some-file.txt'),
|
|
33
|
+
path.join('path/to/fake/dir/first-json-file.json'),
|
|
34
|
+
path.join('path/to/fake/dir/some-file.txt'),
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
it('Returns an empty flat array of jpg files', () => {
|
|
38
|
+
expect((0, listFilesInDir_1.flatListFiles)('path/to/fake/dir', '.jpg')).toStrictEqual([]);
|
|
39
|
+
});
|
|
40
|
+
});
|