1
+ const chalk = require('chalk');
1
2
const inquirer = require("inquirer");
2
3
const JSONbig = require("json-bigint")({ storeAsString: false });
3
4
const { Command } = require("commander");
4
5
const { localConfig, globalConfig } = require("../config");
5
6
const { Spinner, SPINNER_ARC, SPINNER_DOTS } = require('../spinner');
6
7
const { paginate } = require('../paginate');
7
8
const { questionsPushBuckets, questionsPushTeams, questionsPushFunctions, questionsGetEntrypoint, questionsPushCollections, questionsConfirmPushCollections, questionsPushMessagingTopics } = require("../questions");
8
- const { actionRunner, success, log, error, commandDescriptions } = require("../parser");
9
+ const { actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
9
10
const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsGetDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
10
11
const {
11
12
databasesGet,
@@ -140,6 +141,39 @@ const awaitPools = {
140
141
iteration + 1
141
142
);
142
143
},
144
+ deleteAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
145
+ if (iteration > pollMaxDebounces) {
146
+ return false;
147
+ }
148
+
149
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
150
+ if (steps > 1 && iteration === 1) {
151
+ pollMaxDebounces *= steps;
152
+
153
+ log('Found a large number of deleting attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
154
+ }
155
+
156
+ const { attributes } = await paginate(databasesListAttributes, {
157
+ databaseId,
158
+ collectionId,
159
+ parseOutput: false
160
+ }, 100, 'attributes');
161
+
162
+ const ready = attributeKeys.filter(attribute => attributes.includes(attribute.key));
163
+
164
+ if (ready.length === 0) {
165
+ return true;
166
+ }
167
+
168
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
169
+
170
+ return await awaitPools.expectAttributes(
171
+ databaseId,
172
+ collectionId,
173
+ attributeKeys,
174
+ iteration + 1
175
+ );
176
+ },
143
177
expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
144
178
if (iteration > pollMaxDebounces) {
145
179
return false;
@@ -645,6 +679,91 @@ const createAttribute = async (databaseId, collectionId, attribute) => {
645
679
}
646
680
}
647
681
682
+ const deleteAttribute = async (collection, attribute) => {
683
+ log(`Deleting attribute ${attribute.key} of ${collection.name} ( ${collection['$id']} )`);
684
+
685
+ await databasesDeleteAttribute({
686
+ databaseId: collection['databaseId'],
687
+ collectionId: collection['$id'],
688
+ key: attribute.key,
689
+ parseOutput: false
690
+ });
691
+ }
692
+
693
+ const deepSimilar = (remote, local, collection) => {
694
+ if (local === undefined) {
695
+ return undefined;
696
+ }
697
+
698
+ const key = `${chalk.yellow(local.key)} in ${collection.name} (${collection['$id']})`;
699
+
700
+ if (remote.type !== local.type) {
701
+ return { key, attribute: remote, reason: `type changed from ${chalk.red(remote.type)} to ${chalk.green(local.type)}` };
702
+ }
703
+
704
+ if (remote.array !== local.array) {
705
+ return { key, attribute: remote, reason: `array changed from ${chalk.red(remote.array)} to ${chalk.green(local.array)}` };
706
+ }
707
+
708
+ if (remote.size !== local.size) {
709
+ return { key, attribute: remote, reason: `size changed from ${chalk.red(remote.size)} to ${chalk.green(local.size)}` };
710
+ }
711
+
712
+ if (remote.relatedCollectionId !== local.relatedCollectionId) {
713
+ return { key, attribute: remote, reason: `relationships collection id changed from ${chalk.red(remote.relatedCollectionId)} to ${chalk.green(local.relatedCollectionId)}` };
714
+ }
715
+
716
+ if (remote.twoWay !== local.twoWay) {
717
+ return { key, attribute: remote, reason: `relationships twoWay changed from ${chalk.red(remote.twoWay)} to ${chalk.green(local.twoWay)}` };
718
+ }
719
+
720
+ if (remote.twoWayKey !== local.twoWayKey) {
721
+ return { key, attribute: remote, reason: `relationships twoWayKey changed from ${chalk.red(remote.twoWayKey)} to ${chalk.green(local.twoWayKey)}` };
722
+ }
723
+
724
+ return undefined;
725
+ }
726
+
727
+ const findMatch = (attribute, attributes) => attributes.find((attr) => attr.key === attribute.key);
728
+
729
+ const updatedList = async (remoteAttributes, localAttributes, collection) => {
730
+ const deleting = remoteAttributes.filter((attribute) => !findMatch(attribute, localAttributes));
731
+ const changes = remoteAttributes.map((attribute) => deepSimilar(attribute, findMatch(attribute, localAttributes), collection)).filter(attribute => attribute !== undefined);
732
+ let changedAttributes = [];
733
+
734
+ if (changes.length > 0) {
735
+ log('There is a conflict in your collection deployment');
736
+ drawTable(changes.map((change) => {
737
+ return { Key: change.key, Reason: change.reason };
738
+ }));
739
+ const answers = await inquirer.prompt(questionsPushCollections[1]);
740
+
741
+ if (answers.changes.toLowerCase() !== 'yes') {
742
+ return [];
743
+ }
744
+
745
+ changedAttributes = changes.map((change) => change.attribute);
746
+
747
+ await Promise.all(changedAttributes.map((changed) => deleteAttribute(collection, changed)));
748
+
749
+ remoteAttributes = remoteAttributes.filter((attribute) => !findMatch(attribute, changedAttributes))
750
+ }
751
+
752
+ await Promise.all(deleting.map((attribute) => deleteAttribute(collection, attribute)));
753
+
754
+ const attributeKeys = [...remoteAttributes.map(attribute => attribute.key), ...deleting.map(attribute => attribute.key)]
755
+
756
+ if (attributeKeys.length) {
757
+ const deleteAttributesPoolStatus = await awaitPools.deleteAttributes(collection['databaseId'], collection['$id'], attributeKeys);
758
+
759
+ if (!deleteAttributesPoolStatus) {
760
+ throw new Error("Attribute deletion timed out.");
761
+ }
762
+ }
763
+
764
+ return localAttributes.filter((attribute) => !findMatch(attribute, remoteAttributes));
765
+ }
766
+
648
767
const pushCollection = async ({ all, yes } = {}) => {
649
768
const collections = [];
650
769
@@ -666,7 +785,7 @@ const pushCollection = async ({ all, yes } = {}) => {
666
785
}
667
786
const databases = Array.from(new Set(collections.map(c => c['databaseId'])));
668
787
669
- // Parallel db action
788
+ // Parallel db actions
670
789
await Promise.all(databases.map(async (dbId) => {
671
790
const localDatabase = localConfig.getDatabase(dbId);
672
791
@@ -696,7 +815,7 @@ const pushCollection = async ({ all, yes } = {}) => {
696
815
}
697
816
}));
698
817
699
- // Parallel collection action
818
+ // Parallel collection actions
700
819
await Promise.all(collections.map(async (collection) => {
701
820
try {
702
821
const remoteCollection = await databasesGetCollection({
@@ -715,6 +834,9 @@ const pushCollection = async ({ all, yes } = {}) => {
715
834
716
835
success(`Updated ${collection.name} ( ${collection['$id']} ) name`);
717
836
}
837
+ collection.remoteVersion = remoteCollection;
838
+
839
+ collection.isExisted = true;
718
840
} catch (e) {
719
841
if (e.code == 404) {
720
842
log(`Collection ${collection.name} does not exist in the project. Creating ... `);
@@ -726,19 +848,30 @@ const pushCollection = async ({ all, yes } = {}) => {
726
848
permissions: collection['$permissions'],
727
849
parseOutput: false
728
850
})
729
-
851
+ collection.isNew = true;
730
852
} else {
731
853
throw e;
732
854
}
733
855
}
734
856
}))
735
857
736
- // Serialize attribute creation
858
+ // Serialize attribute actions
737
859
for (let collection of collections) {
860
+ let attributes = collection.attributes;
861
+
862
+ if (collection.isExisted) {
863
+ attributes = await updatedList(collection.remoteVersion.attributes, collection.attributes, collection);
864
+
865
+ if (Array.isArray(attributes) && attributes.length < = 0) {
866
+ log(`No changes has been detected. Skipping ${collection.name} ( ${collection['$id']} )`);
867
+ continue;
868
+ }
869
+ }
870
+
738
871
log(`Pushing collection ${collection.name} ( ${collection['databaseId']} - ${collection['$id']} ) attributes`)
739
872
740
873
try {
741
- await createAttributes(collection. attributes, collection)
874
+ await createAttributes(attributes, collection)
742
875
} catch (e) {
743
876
throw e;
744
877
}
0 commit comments