appwrite-cli 6.0.0-rc.4 → 6.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,13 +2,13 @@ const chalk = require('chalk');
2
2
  const inquirer = require("inquirer");
3
3
  const JSONbig = require("json-bigint")({ storeAsString: false });
4
4
  const { Command } = require("commander");
5
- const { localConfig, globalConfig } = require("../config");
5
+ const { localConfig, globalConfig, KeysAttributes, KeysFunction, whitelistKeys, KeysTopics, KeysStorage, KeysTeams, } = require("../config");
6
6
  const { Spinner, SPINNER_ARC, SPINNER_DOTS } = require('../spinner');
7
7
  const { paginate } = require('../paginate');
8
- const { questionsPushBuckets, questionsPushTeams, questionsPushFunctions, questionsGetEntrypoint, questionsPushCollections, questionsConfirmPushCollections, questionsPushMessagingTopics, questionsPushResources } = require("../questions");
9
- const { cliConfig, actionRunner, success, warn, log, error, commandDescriptions, drawTable } = require("../parser");
8
+ const { questionsPushBuckets, questionsPushTeams, questionsPushFunctions, questionsGetEntrypoint, questionsPushCollections, questionPushChanges, questionsPushMessagingTopics, questionsPushResources } = require("../questions");
9
+ const { cliConfig, actionRunner, success, warn, log, hint, error, commandDescriptions, drawTable } = require("../parser");
10
10
  const { proxyListRules } = require('./proxy');
11
- const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsGetDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
11
+ const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsGetDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
12
12
  const {
13
13
  databasesGet,
14
14
  databasesCreate,
@@ -37,6 +37,7 @@ const {
37
37
  databasesUpdateRelationshipAttribute,
38
38
  databasesCreateRelationshipAttribute,
39
39
  databasesDeleteAttribute,
40
+ databasesDeleteIndex,
40
41
  databasesListAttributes,
41
42
  databasesListIndexes,
42
43
  databasesUpdateCollection
@@ -53,6 +54,7 @@ const {
53
54
  teamsCreate
54
55
  } = require("./teams");
55
56
  const {
57
+ projectsGet,
56
58
  projectsUpdate,
57
59
  projectsUpdateServiceStatus,
58
60
  projectsUpdateAuthStatus,
@@ -68,8 +70,9 @@ const { checkDeployConditions } = require('../utils');
68
70
  const STEP_SIZE = 100; // Resources
69
71
  const POLL_DEBOUNCE = 2000; // Milliseconds
70
72
  const POLL_MAX_DEBOUNCE = 1800; // Times of POLL_DEBOUNCE (1 hour)
73
+ const POLL_DEFAULT_VALUE = 30;
71
74
 
72
- let pollMaxDebounces = 30;
75
+ let pollMaxDebounces = POLL_DEFAULT_VALUE;
73
76
 
74
77
  const changeableKeys = ['status', 'required', 'xdefault', 'elements', 'min', 'max', 'default', 'error'];
75
78
 
@@ -90,11 +93,13 @@ const awaitPools = {
90
93
  return true;
91
94
  }
92
95
 
93
- let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
94
- if (steps > 1 && iteration === 1) {
95
- pollMaxDebounces *= steps;
96
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
97
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
98
+ if (steps > 1 && iteration === 1) {
99
+ pollMaxDebounces *= steps;
96
100
 
97
- log('Found a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
101
+ log('Found a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
102
+ }
98
103
  }
99
104
 
100
105
  await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
@@ -121,11 +126,13 @@ const awaitPools = {
121
126
  return true;
122
127
  }
123
128
 
124
- let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
125
- if (steps > 1 && iteration === 1) {
126
- pollMaxDebounces *= steps;
129
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
130
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
131
+ if (steps > 1 && iteration === 1) {
132
+ pollMaxDebounces *= steps;
127
133
 
128
- log('Found a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
134
+ log('Found a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
135
+ }
129
136
  }
130
137
 
131
138
  await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
@@ -151,11 +158,13 @@ const awaitPools = {
151
158
  return true;
152
159
  }
153
160
 
154
- let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
155
- if (steps > 1 && iteration === 1) {
156
- pollMaxDebounces *= steps;
161
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
162
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
163
+ if (steps > 1 && iteration === 1) {
164
+ pollMaxDebounces *= steps;
157
165
 
158
- log('Found a large number of variables, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
166
+ log('Found a large number of variables, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
167
+ }
159
168
  }
160
169
 
161
170
  await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
@@ -170,11 +179,13 @@ const awaitPools = {
170
179
  return false;
171
180
  }
172
181
 
173
- let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
174
- if (steps > 1 && iteration === 1) {
175
- pollMaxDebounces *= steps;
182
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
183
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
184
+ if (steps > 1 && iteration === 1) {
185
+ pollMaxDebounces *= steps;
176
186
 
177
- log('Found a large number of attributes to be deleted. Increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
187
+ log('Found a large number of attributes to be deleted. Increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
188
+ }
178
189
  }
179
190
 
180
191
  const { attributes } = await paginate(databasesListAttributes, {
@@ -203,11 +214,13 @@ const awaitPools = {
203
214
  return false;
204
215
  }
205
216
 
206
- let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
207
- if (steps > 1 && iteration === 1) {
208
- pollMaxDebounces *= steps;
217
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
218
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
219
+ if (steps > 1 && iteration === 1) {
220
+ pollMaxDebounces *= steps;
209
221
 
210
- log('Creating a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
222
+ log('Creating a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
223
+ }
211
224
  }
212
225
 
213
226
  const { attributes } = await paginate(databasesListAttributes, {
@@ -243,16 +256,53 @@ const awaitPools = {
243
256
  iteration + 1
244
257
  );
245
258
  },
259
+ deleteIndexes: async (databaseId, collectionId, indexesKeys, iteration = 1) => {
260
+ if (iteration > pollMaxDebounces) {
261
+ return false;
262
+ }
263
+
264
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
265
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
266
+ if (steps > 1 && iteration === 1) {
267
+ pollMaxDebounces *= steps;
268
+
269
+ log('Found a large number of indexes to be deleted. Increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
270
+ }
271
+ }
272
+
273
+ const { indexes } = await paginate(databasesListIndexes, {
274
+ databaseId,
275
+ collectionId,
276
+ parseOutput: false
277
+ }, 100, 'indexes');
278
+
279
+ const ready = indexesKeys.filter(index => indexes.includes(index.key));
280
+
281
+ if (ready.length === 0) {
282
+ return true;
283
+ }
284
+
285
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
286
+
287
+ return await awaitPools.expectIndexes(
288
+ databaseId,
289
+ collectionId,
290
+ indexesKeys,
291
+ iteration + 1
292
+ );
293
+ },
246
294
  expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => {
247
295
  if (iteration > pollMaxDebounces) {
248
296
  return false;
249
297
  }
250
298
 
251
- let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE));
252
- if (steps > 1 && iteration === 1) {
253
- pollMaxDebounces *= steps;
299
+ if (pollMaxDebounces === POLL_DEFAULT_VALUE) {
300
+ let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE));
301
+ if (steps > 1 && iteration === 1) {
302
+ pollMaxDebounces *= steps;
254
303
 
255
- log('Creating a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
304
+ log('Creating a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
305
+ }
256
306
  }
257
307
 
258
308
  const { indexes } = await paginate(databasesListIndexes, {
@@ -290,6 +340,73 @@ const awaitPools = {
290
340
  },
291
341
  }
292
342
 
343
+ const approveChanges = async (resource, resourceGetFunction, keys, resourceName, resourcePlural) => {
344
+ log('Checking for changes');
345
+ const changes = [];
346
+
347
+ await Promise.all(resource.map(async (localResource) => {
348
+ try {
349
+ const remoteResource = await resourceGetFunction({
350
+ [resourceName]: localResource['$id'],
351
+ parseOutput: false,
352
+ });
353
+
354
+ for (let [key, value] of Object.entries(whitelistKeys(remoteResource, keys))) {
355
+ if (Array.isArray(value) && Array.isArray(localResource[key])) {
356
+ if (JSON.stringify(value) !== JSON.stringify(localResource[key])) {
357
+ changes.push({
358
+ id: localResource['$id'],
359
+ key,
360
+ remote: chalk.red(value.join('\n')),
361
+ local: chalk.green(localResource[key].join('\n'))
362
+ })
363
+ }
364
+ } else if (value !== localResource[key]) {
365
+ changes.push({
366
+ id: localResource['$id'],
367
+ key,
368
+ remote: chalk.red(value),
369
+ local: chalk.green(localResource[key])
370
+ })
371
+ }
372
+ }
373
+ } catch (e) {
374
+ if (Number(e.code) !== 404) {
375
+ throw e;
376
+ }
377
+ }
378
+ }));
379
+
380
+ if (changes.length === 0) {
381
+ return true;
382
+ }
383
+
384
+ drawTable(changes);
385
+ if (!cliConfig.force) {
386
+ const answers = await inquirer.prompt(questionPushChanges);
387
+ if (answers.changes.toLowerCase() === 'yes') {
388
+ return true;
389
+ }
390
+ }
391
+
392
+ success(`Successfully pushed 0 ${resourcePlural}.`);
393
+ return false;
394
+ }
395
+
396
+ const getObjectChanges = (remote, local, index, what) => {
397
+ const changes = [];
398
+
399
+ if (remote[index] && local[index]) {
400
+ for (let [service, status] of Object.entries(remote[index])) {
401
+ if (status !== local[index][service]) {
402
+ changes.push({ group: what,setting: service, remote: chalk.red(status), local: chalk.green(local[index][service]) })
403
+ }
404
+ }
405
+ }
406
+
407
+ return changes;
408
+ }
409
+
293
410
  const createAttribute = async (databaseId, collectionId, attribute) => {
294
411
  switch (attribute.type) {
295
412
  case 'string':
@@ -523,8 +640,18 @@ const updateAttribute = async (databaseId, collectionId, attribute) => {
523
640
  })
524
641
  }
525
642
  }
526
- const deleteAttribute = async (collection, attribute) => {
527
- log(`Deleting attribute ${attribute.key} of ${collection.name} ( ${collection['$id']} )`);
643
+ const deleteAttribute = async (collection, attribute, isIndex = false) => {
644
+ log(`Deleting ${isIndex ? 'index' : 'attribute'} ${attribute.key} of ${collection.name} ( ${collection['$id']} )`);
645
+
646
+ if (isIndex) {
647
+ await databasesDeleteIndex({
648
+ databaseId: collection['databaseId'],
649
+ collectionId: collection['$id'],
650
+ key: attribute.key,
651
+ parseOutput: false
652
+ });
653
+ return;
654
+ }
528
655
 
529
656
  await databasesDeleteAttribute({
530
657
  databaseId: collection['databaseId'],
@@ -555,6 +682,10 @@ const checkAttributeChanges = (remote, local, collection, recraeting = true) =>
555
682
  let attribute = remote;
556
683
 
557
684
  for (let key of Object.keys(remote)) {
685
+ if (!KeysAttributes.has(key)) {
686
+ continue;
687
+ }
688
+
558
689
  if (changeableKeys.includes(key)) {
559
690
  if (!recraeting) {
560
691
  if (remote[key] !== local[key]) {
@@ -570,7 +701,12 @@ const checkAttributeChanges = (remote, local, collection, recraeting = true) =>
570
701
  continue;
571
702
  }
572
703
 
573
- if (remote[key] !== local[key]) {
704
+ if (Array.isArray(remote[key]) && Array.isArray(local[key])) {
705
+ if (JSON.stringify(remote[key]) !== JSON.stringify(local[key])) {
706
+ const bol = reason === '' ? '' : '\n';
707
+ reason += `${bol}${key} changed from ${chalk.red(remote[key])} to ${chalk.green(local[key])}`;
708
+ }
709
+ } else if (remote[key] !== local[key]) {
574
710
  const bol = reason === '' ? '' : '\n';
575
711
  reason += `${bol}${key} changed from ${chalk.red(remote[key])} to ${chalk.green(local[key])}`;
576
712
  }
@@ -590,7 +726,7 @@ const generateChangesObject = (attribute, collection, isAdding) => {
590
726
  return {
591
727
  key: `${chalk.yellow(attribute.key)} in ${collection.name} (${collection['$id']})`,
592
728
  attribute: attribute,
593
- reason: isAdding ? 'Field doesn\'t exist on the remote server' : 'Field doesn\'t exist in appwrite.json file',
729
+ reason: isAdding ? 'Field isn\'t present on the remote server' : 'Field isn\'t present on the appwrite.json file',
594
730
  action: isAdding ? chalk.green('adding') : chalk.red('deleting')
595
731
  };
596
732
 
@@ -599,12 +735,9 @@ const generateChangesObject = (attribute, collection, isAdding) => {
599
735
  /**
600
736
  * Filter deleted and recreated attributes,
601
737
  * return list of attributes to create
602
- * @param remoteAttributes
603
- * @param localAttributes
604
- * @param collection
605
738
  * @returns {Promise<*|*[]>}
606
739
  */
607
- const attributesToCreate = async (remoteAttributes, localAttributes, collection) => {
740
+ const attributesToCreate = async (remoteAttributes, localAttributes, collection, isIndex = false) => {
608
741
 
609
742
  const deleting = remoteAttributes.filter((attribute) => !attributesContains(attribute, localAttributes)).map((attr) => generateChangesObject(attr, collection, false));
610
743
  const adding = localAttributes.filter((attribute) => !attributesContains(attribute, remoteAttributes)).map((attr) => generateChangesObject(attr, collection, true));
@@ -626,14 +759,14 @@ const attributesToCreate = async (remoteAttributes, localAttributes, collection)
626
759
  }));
627
760
 
628
761
  if (!cliConfig.force) {
629
- if (deleting.length > 0) {
762
+ if (deleting.length > 0 && !isIndex) {
630
763
  log(`Attribute deletion will cause ${chalk.red('loss of data')}`);
631
764
  }
632
- if (conflicts.length > 0) {
765
+ if (conflicts.length > 0 && !isIndex) {
633
766
  log(`Attribute recreation will cause ${chalk.red('loss of data')}`);
634
767
  }
635
768
 
636
- const answers = await inquirer.prompt(questionsPushCollections[1]);
769
+ const answers = await inquirer.prompt(questionPushChanges);
637
770
 
638
771
  if (answers.changes.toLowerCase() !== 'yes') {
639
772
  return changedAttributes;
@@ -642,17 +775,17 @@ const attributesToCreate = async (remoteAttributes, localAttributes, collection)
642
775
 
643
776
  if (conflicts.length > 0) {
644
777
  changedAttributes = conflicts.map((change) => change.attribute);
645
- await Promise.all(changedAttributes.map((changed) => deleteAttribute(collection, changed)));
778
+ await Promise.all(changedAttributes.map((changed) => deleteAttribute(collection, changed, isIndex)));
646
779
  remoteAttributes = remoteAttributes.filter((attribute) => !attributesContains(attribute, changedAttributes))
647
780
  }
648
781
 
649
782
  if (changes.length > 0) {
650
783
  changedAttributes = changes.map((change) => change.attribute);
651
- await Promise.all(changedAttributes.map((changed) => updateAttribute(collection['databaseId'],collection['$id'], changed)));
784
+ await Promise.all(changedAttributes.map((changed) => updateAttribute(collection['databaseId'], collection['$id'], changed)));
652
785
  }
653
786
 
654
787
  const deletingAttributes = deleting.map((change) => change.attribute);
655
- await Promise.all(deletingAttributes.map((attribute) => deleteAttribute(collection, attribute)));
788
+ await Promise.all(deletingAttributes.map((attribute) => deleteAttribute(collection, attribute, isIndex)));
656
789
  const attributeKeys = [...remoteAttributes.map(attribute => attribute.key), ...deletingAttributes.map(attribute => attribute.key)]
657
790
 
658
791
  if (attributeKeys.length) {
@@ -737,6 +870,37 @@ const pushResources = async () => {
737
870
  };
738
871
 
739
872
  const pushSettings = async () => {
873
+ checkDeployConditions(localConfig);
874
+
875
+ try {
876
+ let response = await projectsGet({
877
+ parseOutput: false,
878
+ projectId: localConfig.getProject().projectId
879
+ });
880
+
881
+ const remoteSettings = localConfig.createSettingsObject(response ?? {});
882
+ const localSettings = localConfig.getProject().projectSettings ?? {};
883
+
884
+ log('Checking for changes');
885
+ const changes = [];
886
+
887
+ changes.push(...(getObjectChanges(remoteSettings, localSettings, 'services', 'Service')));
888
+ changes.push(...(getObjectChanges(remoteSettings['auth'] ?? {}, localSettings['auth'] ?? {}, 'methods', 'Auth method')));
889
+ changes.push(...(getObjectChanges(remoteSettings['auth'] ?? {}, localSettings['auth'] ?? {}, 'security', 'Auth security')));
890
+
891
+ if (changes.length > 0) {
892
+ drawTable(changes);
893
+ if (!cliConfig.force) {
894
+ const answers = await inquirer.prompt(questionPushChanges);
895
+ if (answers.changes.toLowerCase() !== 'yes') {
896
+ success(`Successfully pushed 0 project settings.`);
897
+ return;
898
+ }
899
+ }
900
+ }
901
+ } catch (e) {
902
+ }
903
+
740
904
  try {
741
905
  log("Pushing project settings ...");
742
906
 
@@ -796,9 +960,7 @@ const pushSettings = async () => {
796
960
  }
797
961
  }
798
962
 
799
- const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero: false }) => {
800
- let response = {};
801
-
963
+ const pushFunction = async ({ functionId, async, code } = { returnOnZero: false }) => {
802
964
  const functionIds = [];
803
965
 
804
966
  if (functionId) {
@@ -806,11 +968,6 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
806
968
  } else if (cliConfig.all) {
807
969
  checkDeployConditions(localConfig);
808
970
  const functions = localConfig.getFunctions();
809
- if (functions.length === 0) {
810
- log("No functions found.");
811
- hint("Use 'appwrite pull functions' to synchronize existing one, or use 'appwrite init function' to create a new one.");
812
- return;
813
- }
814
971
  functionIds.push(...functions.map((func) => {
815
972
  return func.$id;
816
973
  }));
@@ -818,7 +975,15 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
818
975
 
819
976
  if (functionIds.length <= 0) {
820
977
  const answers = await inquirer.prompt(questionsPushFunctions[0]);
821
- functionIds.push(...answers.functions);
978
+ if (answers.functions) {
979
+ functionIds.push(...answers.functions);
980
+ }
981
+ }
982
+
983
+ if (functionIds.length === 0) {
984
+ log("No functions found.");
985
+ hint("Use 'appwrite pull functions' to synchronize existing one, or use 'appwrite init function' to create a new one.");
986
+ return;
822
987
  }
823
988
 
824
989
  let functions = functionIds.map((id) => {
@@ -844,6 +1009,10 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
844
1009
  }
845
1010
  }
846
1011
 
1012
+ if (!(await approveChanges(functions, functionsGet, KeysFunction, 'functionId', 'functions'))) {
1013
+ return;
1014
+ }
1015
+
847
1016
  log('Pushing functions ...');
848
1017
 
849
1018
  Spinner.start(false);
@@ -852,6 +1021,8 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
852
1021
  const failedDeployments = [];
853
1022
 
854
1023
  await Promise.all(functions.map(async (func) => {
1024
+ let response = {};
1025
+
855
1026
  const ignore = func.ignore ? 'appwrite.json' : '.gitignore';
856
1027
  let functionExists = false;
857
1028
  let deploymentCreated = false;
@@ -859,7 +1030,6 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
859
1030
  const updaterRow = new Spinner({ status: '', resource: func.name, id: func['$id'], end: `Ignoring using: ${ignore}` });
860
1031
 
861
1032
  updaterRow.update({ status: 'Getting' }).startSpinner(SPINNER_DOTS);
862
-
863
1033
  try {
864
1034
  response = await functionsGet({
865
1035
  functionId: func['$id'],
@@ -930,6 +1100,14 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
930
1100
  }
931
1101
  }
932
1102
 
1103
+ if (code === false) {
1104
+ successfullyPushed++;
1105
+ successfullyDeployed++;
1106
+ updaterRow.update({ status: 'Pushed' });
1107
+ updaterRow.stopSpinner();
1108
+ return;
1109
+ }
1110
+
933
1111
  try {
934
1112
  updaterRow.update({ status: 'Pushing' }).replaceSpinner(SPINNER_ARC);
935
1113
  response = await functionsCreateDeployment({
@@ -961,11 +1139,6 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
961
1139
  let pollChecks = 0;
962
1140
 
963
1141
  while (true) {
964
- if (pollChecks >= POLL_MAX_DEBOUNCE) {
965
- updaterRow.update({ end: 'Deployment is taking too long. Please check the console for more details.' })
966
- break;
967
- }
968
-
969
1142
  response = await functionsGetDeployment({
970
1143
  functionId: func['$id'],
971
1144
  deploymentId: deploymentId,
@@ -1004,7 +1177,7 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
1004
1177
  }
1005
1178
 
1006
1179
  pollChecks++;
1007
- await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
1180
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE * 1.5));
1008
1181
  }
1009
1182
  } catch (e) {
1010
1183
  updaterRow.fail({ errorMessage: e.message ?? 'Unknown error occurred. Please try again' })
@@ -1018,15 +1191,15 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
1018
1191
 
1019
1192
  failedDeployments.forEach((failed) => {
1020
1193
  const { name, deployment, $id } = failed;
1021
- const failUrl = `${globalConfig.getEndpoint().replace('/v1', '')}/console/project-${localConfig.getProject().projectId}/functions/function-${$id}/deployment-${deployment}`;
1194
+ const failUrl = `${globalConfig.getEndpoint().slice(0, -3)}/console/project-${localConfig.getProject().projectId}/functions/function-${$id}/deployment-${deployment}`;
1022
1195
 
1023
1196
  error(`Deployment of ${name} has failed. Check at ${failUrl} for more details\n`);
1024
1197
  });
1025
1198
 
1026
1199
  if (!async) {
1027
- if(successfullyPushed === 0) {
1200
+ if (successfullyPushed === 0) {
1028
1201
  error('No functions were pushed.');
1029
- } else if(successfullyDeployed != successfullyPushed) {
1202
+ } else if (successfullyDeployed !== successfullyPushed) {
1030
1203
  warn(`Successfully pushed ${successfullyDeployed} of ${successfullyPushed} functions`)
1031
1204
  } else {
1032
1205
  success(`Successfully pushed ${successfullyPushed} functions.`);
@@ -1036,28 +1209,36 @@ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero
1036
1209
  }
1037
1210
  }
1038
1211
 
1039
- const pushCollection = async ({ returnOnZero } = { returnOnZero: false }) => {
1212
+ const pushCollection = async ({ returnOnZero, attempts } = { returnOnZero: false }) => {
1040
1213
  const collections = [];
1041
1214
 
1215
+ if (attempts) {
1216
+ pollMaxDebounces = attempts;
1217
+ }
1218
+
1042
1219
  if (cliConfig.all) {
1043
1220
  checkDeployConditions(localConfig);
1044
- if (localConfig.getCollections().length === 0) {
1045
- log("No collections found.");
1046
- hint("Use 'appwrite pull collections' to synchronize existing one, or use 'appwrite init collection' to create a new one.");
1047
- return;
1048
- }
1049
1221
  collections.push(...localConfig.getCollections());
1050
1222
  } else {
1051
- const answers = await inquirer.prompt(questionsPushCollections[0])
1052
- const configCollections = new Map();
1053
- localConfig.getCollections().forEach((c) => {
1054
- configCollections.set(`${c['databaseId']}|${c['$id']}`, c);
1055
- });
1056
- answers.collections.forEach((a) => {
1057
- const collection = configCollections.get(a);
1058
- collections.push(collection);
1059
- })
1223
+ const answers = await inquirer.prompt(questionsPushCollections)
1224
+ if (answers.collections) {
1225
+ const configCollections = new Map();
1226
+ localConfig.getCollections().forEach((c) => {
1227
+ configCollections.set(`${c['databaseId']}|${c['$id']}`, c);
1228
+ });
1229
+ answers.collections.forEach((a) => {
1230
+ const collection = configCollections.get(a);
1231
+ collections.push(collection);
1232
+ })
1233
+ }
1234
+ }
1235
+
1236
+ if (collections.length === 0) {
1237
+ log("No collections found.");
1238
+ hint("Use 'appwrite pull collections' to synchronize existing one, or use 'appwrite init collection' to create a new one.");
1239
+ return;
1060
1240
  }
1241
+
1061
1242
  const databases = Array.from(new Set(collections.map(collection => collection['databaseId'])));
1062
1243
 
1063
1244
  log('Checking for changes ...');
@@ -1132,17 +1313,20 @@ const pushCollection = async ({ returnOnZero } = { returnOnZero: false }) => {
1132
1313
  }
1133
1314
  }
1134
1315
  }))
1135
-
1316
+ let numberOfCollections = 0;
1136
1317
  // Serialize attribute actions
1137
1318
  for (let collection of collections) {
1138
1319
  let attributes = collection.attributes;
1320
+ let indexes = collection.indexes;
1139
1321
 
1140
1322
  if (collection.isExisted) {
1141
1323
  attributes = await attributesToCreate(collection.remoteVersion.attributes, collection.attributes, collection);
1324
+ indexes = await attributesToCreate(collection.remoteVersion.indexes, collection.indexes, collection, true);
1142
1325
 
1143
- if (Array.isArray(attributes) && attributes.length <= 0) {
1326
+ if ((Array.isArray(attributes) && attributes.length <= 0) && (Array.isArray(indexes) && indexes.length <= 0)) {
1144
1327
  continue;
1145
1328
  }
1329
+
1146
1330
  }
1147
1331
 
1148
1332
  log(`Pushing collection ${collection.name} ( ${collection['databaseId']} - ${collection['$id']} ) attributes`)
@@ -1154,13 +1338,15 @@ const pushCollection = async ({ returnOnZero } = { returnOnZero: false }) => {
1154
1338
  }
1155
1339
 
1156
1340
  try {
1157
- await createIndexes(collection.indexes, collection);
1341
+ await createIndexes(indexes, collection);
1158
1342
  } catch (e) {
1159
1343
  throw e;
1160
1344
  }
1161
-
1345
+ numberOfCollections++;
1162
1346
  success(`Pushed ${collection.name} ( ${collection['$id']} )`);
1163
1347
  }
1348
+
1349
+ success(`Pushed ${numberOfCollections} collections`);
1164
1350
  }
1165
1351
 
1166
1352
  const pushBucket = async ({ returnOnZero } = { returnOnZero: false }) => {
@@ -1171,17 +1357,20 @@ const pushBucket = async ({ returnOnZero } = { returnOnZero: false }) => {
1171
1357
 
1172
1358
  if (cliConfig.all) {
1173
1359
  checkDeployConditions(localConfig);
1174
- if (configBuckets.length === 0) {
1175
- log("No buckets found.");
1176
- hint("Use 'appwrite pull buckets' to synchronize existing one, or use 'appwrite init bucket' to create a new one.");
1177
- return;
1178
- }
1179
1360
  bucketIds.push(...configBuckets.map((b) => b.$id));
1180
1361
  }
1181
1362
 
1182
1363
  if (bucketIds.length === 0) {
1183
1364
  const answers = await inquirer.prompt(questionsPushBuckets[0])
1184
- bucketIds.push(...answers.buckets);
1365
+ if (answers.buckets) {
1366
+ bucketIds.push(...answers.buckets);
1367
+ }
1368
+ }
1369
+
1370
+ if (bucketIds.length === 0) {
1371
+ log("No buckets found.");
1372
+ hint("Use 'appwrite pull buckets' to synchronize existing one, or use 'appwrite init bucket' to create a new one.");
1373
+ return;
1185
1374
  }
1186
1375
 
1187
1376
  let buckets = [];
@@ -1191,6 +1380,10 @@ const pushBucket = async ({ returnOnZero } = { returnOnZero: false }) => {
1191
1380
  buckets.push(...idBuckets);
1192
1381
  }
1193
1382
 
1383
+ if (!(await approveChanges(buckets, storageGetBucket, KeysStorage, 'bucketId', 'buckets'))) {
1384
+ return;
1385
+ }
1386
+
1194
1387
  log('Pushing buckets ...');
1195
1388
 
1196
1389
  for (let bucket of buckets) {
@@ -1249,16 +1442,20 @@ const pushTeam = async ({ returnOnZero } = { returnOnZero: false }) => {
1249
1442
 
1250
1443
  if (cliConfig.all) {
1251
1444
  checkDeployConditions(localConfig);
1252
- if (configTeams.length === 0) {
1253
- log("No teams found.");
1254
- hint("Use 'appwrite pull teams' to synchronize existing one, or use 'appwrite init team' to create a new one.");
1255
- }
1256
1445
  teamIds.push(...configTeams.map((t) => t.$id));
1257
1446
  }
1258
1447
 
1259
1448
  if (teamIds.length === 0) {
1260
1449
  const answers = await inquirer.prompt(questionsPushTeams[0])
1261
- teamIds.push(...answers.teams);
1450
+ if (answers.teams) {
1451
+ teamIds.push(...answers.teams);
1452
+ }
1453
+ }
1454
+
1455
+ if (teamIds.length === 0) {
1456
+ log("No teams found.");
1457
+ hint("Use 'appwrite pull teams' to synchronize existing one, or use 'appwrite init team' to create a new one.");
1458
+ return;
1262
1459
  }
1263
1460
 
1264
1461
  let teams = [];
@@ -1268,6 +1465,11 @@ const pushTeam = async ({ returnOnZero } = { returnOnZero: false }) => {
1268
1465
  teams.push(...idTeams);
1269
1466
  }
1270
1467
 
1468
+ if (!(await approveChanges(teams, teamsGet, KeysTeams, 'teamId', 'teams'))) {
1469
+ return;
1470
+ }
1471
+
1472
+
1271
1473
  log('Pushing teams ...');
1272
1474
 
1273
1475
  for (let team of teams) {
@@ -1311,16 +1513,20 @@ const pushMessagingTopic = async ({ returnOnZero } = { returnOnZero: false }) =>
1311
1513
 
1312
1514
  if (cliConfig.all) {
1313
1515
  checkDeployConditions(localConfig);
1314
- if (configTopics.length === 0) {
1315
- log("No topics found.");
1316
- hint("Use 'appwrite pull topics' to synchronize existing one, or use 'appwrite init topic' to create a new one.");
1317
- }
1318
1516
  topicsIds.push(...configTopics.map((b) => b.$id));
1319
1517
  }
1320
1518
 
1321
1519
  if (topicsIds.length === 0) {
1322
1520
  const answers = await inquirer.prompt(questionsPushMessagingTopics[0])
1323
- topicsIds.push(...answers.topics);
1521
+ if (answers.topics) {
1522
+ topicsIds.push(...answers.topics);
1523
+ }
1524
+ }
1525
+
1526
+ if (topicsIds.length === 0) {
1527
+ log("No topics found.");
1528
+ hint("Use 'appwrite pull topics' to synchronize existing one, or use 'appwrite init topic' to create a new one.");
1529
+ return;
1324
1530
  }
1325
1531
 
1326
1532
  let topics = [];
@@ -1337,6 +1543,10 @@ const pushMessagingTopic = async ({ returnOnZero } = { returnOnZero: false }) =>
1337
1543
  }
1338
1544
  }
1339
1545
 
1546
+ if (!(await approveChanges(topics, messagingGetTopic, KeysTopics, 'topicId', 'topics'))) {
1547
+ return;
1548
+ }
1549
+
1340
1550
  log('Pushing topics ...');
1341
1551
 
1342
1552
  for (let topic of topics) {
@@ -1402,14 +1612,16 @@ push
1402
1612
  .command("function")
1403
1613
  .alias("functions")
1404
1614
  .description("Push functions in the current directory.")
1405
- .option(`-f, --functionId <functionId>`, `Function ID`)
1615
+ .option(`-f, --function-id <function-id>`, `ID of function to run`)
1406
1616
  .option(`-A, --async`, `Don't wait for functions deployments status`)
1617
+ .option("--no-code", "Don't push the function's code")
1407
1618
  .action(actionRunner(pushFunction));
1408
1619
 
1409
1620
  push
1410
1621
  .command("collection")
1411
1622
  .alias("collections")
1412
1623
  .description("Push collections in the current project.")
1624
+ .option(`-a, --attempts <numberOfAttempts>`, `Max number of attempts before timing out. default: 30.`)
1413
1625
  .action(actionRunner(pushCollection));
1414
1626
 
1415
1627
  push