appwrite-cli 4.1.0 → 4.2.0

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,6 +2,7 @@ const inquirer = require("inquirer");
2
2
  const JSONbig = require("json-bigint")({ storeAsString: false });
3
3
  const { Command } = require("commander");
4
4
  const { localConfig } = require("../config");
5
+ const { paginate } = require('../paginate');
5
6
  const { questionsDeployBuckets, questionsDeployTeams, questionsDeployFunctions, questionsGetEntrypoint, questionsDeployCollections, questionsConfirmDeployCollections } = require("../questions");
6
7
  const { actionRunner, success, log, error, commandDescriptions } = require("../parser");
7
8
  const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
@@ -37,113 +38,163 @@ const {
37
38
  teamsCreate
38
39
  } = require("./teams");
39
40
 
40
- const POOL_DEBOUNCE = 2000; // in milliseconds
41
- const POOL_MAX_DEBOUNCES = 30;
41
+ const STEP_SIZE = 100; // Resources
42
+ const POOL_DEBOUNCE = 2000; // Milliseconds
43
+
44
+ let poolMaxDebounces = 30;
42
45
 
43
46
  const awaitPools = {
44
47
  wipeAttributes: async (databaseId, collectionId, iteration = 1) => {
45
- if (iteration > POOL_MAX_DEBOUNCES) {
48
+ if (iteration > poolMaxDebounces) {
46
49
  return false;
47
50
  }
48
51
 
49
- // TODO: Pagination?
50
- const { attributes: remoteAttributes } = await databasesListAttributes({
52
+ const { total } = await databasesListAttributes({
51
53
  databaseId,
52
54
  collectionId,
53
- queries: ['limit(100)'],
55
+ queries: ['limit(1)'],
54
56
  parseOutput: false
55
57
  });
56
58
 
57
- if (remoteAttributes.length <= 0) {
59
+ if (total === 0) {
58
60
  return true;
59
61
  }
60
62
 
63
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
64
+ if (steps > 1 && iteration === 1) {
65
+ poolMaxDebounces *= steps;
66
+
67
+ log('Found a large number of attributes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
68
+ }
69
+
61
70
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
62
- return await awaitPools.wipeAttributes(databaseId, collectionId, iteration + 1);
71
+
72
+ return await awaitPools.wipeAttributes(
73
+ databaseId,
74
+ collectionId,
75
+ iteration + 1
76
+ );
63
77
  },
64
78
  wipeIndexes: async (databaseId, collectionId, iteration = 1) => {
65
- if (iteration > POOL_MAX_DEBOUNCES) {
79
+ if (iteration > poolMaxDebounces) {
66
80
  return false;
67
81
  }
68
82
 
69
- // TODO: Pagination?
70
- const { indexes: remoteIndexes } = await databasesListIndexes({
83
+ const { total } = await databasesListIndexes({
71
84
  databaseId,
72
85
  collectionId,
73
86
  queries: ['limit(100)'],
74
87
  parseOutput: false
75
88
  });
76
89
 
77
- if (remoteIndexes.length <= 0) {
90
+ if (total === 0) {
78
91
  return true;
79
92
  }
80
93
 
94
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
95
+ if (steps > 1 && iteration === 1) {
96
+ poolMaxDebounces *= steps;
97
+
98
+ log('Found a large number of indexes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
99
+ }
100
+
81
101
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
82
- return await awaitPools.wipeIndexes(databaseId, collectionId, iteration + 1);
102
+
103
+ return await awaitPools.wipeIndexes(
104
+ databaseId,
105
+ collectionId,
106
+ iteration + 1
107
+ );
83
108
  },
84
109
  expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
85
- if (iteration > POOL_MAX_DEBOUNCES) {
110
+ if (iteration > poolMaxDebounces) {
86
111
  return false;
87
112
  }
88
113
 
89
- // TODO: Pagination?
90
- const { attributes: remoteAttributes } = await databasesListAttributes({
114
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
115
+ if (steps > 1 && iteration === 1) {
116
+ poolMaxDebounces *= steps;
117
+
118
+ log('Creating a large number of attributes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
119
+ }
120
+
121
+ const { attributes } = await paginate(databasesListAttributes, {
91
122
  databaseId,
92
123
  collectionId,
93
- queries: ['limit(100)'],
94
124
  parseOutput: false
95
- });
125
+ }, 100, 'attributes');
96
126
 
97
- const readyAttributeKeys = remoteAttributes.filter((attribute) => {
98
- if (attributeKeys.includes(attribute.key)) {
99
- if (['stuck', 'failed'].includes(attribute.status)) {
100
- throw new Error(`Attribute '${attribute.key}' failed!`);
101
- }
127
+ const ready = attributes
128
+ .filter(attribute => {
129
+ if (attributeKeys.includes(attribute.key)) {
130
+ if (['stuck', 'failed'].includes(attribute.status)) {
131
+ throw new Error(`Attribute '${attribute.key}' failed!`);
132
+ }
102
133
 
103
- return attribute.status === 'available';
104
- }
134
+ return attribute.status === 'available';
135
+ }
105
136
 
106
- return false;
107
- }).map(attribute => attribute.key);
137
+ return false;
138
+ })
139
+ .map(attribute => attribute.key);
108
140
 
109
- if (readyAttributeKeys.length >= attributeKeys.length) {
141
+ if (ready.length === attributeKeys.length) {
110
142
  return true;
111
143
  }
112
144
 
113
145
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
114
- return await awaitPools.expectAttributes(databaseId, collectionId, attributeKeys, iteration + 1);
146
+
147
+ return await awaitPools.expectAttributes(
148
+ databaseId,
149
+ collectionId,
150
+ attributeKeys,
151
+ iteration + 1
152
+ );
115
153
  },
116
154
  expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => {
117
- if (iteration > POOL_MAX_DEBOUNCES) {
155
+ if (iteration > poolMaxDebounces) {
118
156
  return false;
119
157
  }
120
158
 
121
- // TODO: Pagination?
122
- const { indexes: remoteIndexes } = await databasesListIndexes({
159
+ let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE));
160
+ if (steps > 1 && iteration === 1) {
161
+ poolMaxDebounces *= steps;
162
+
163
+ log('Creating a large number of indexes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes')
164
+ }
165
+
166
+ const { indexes } = await paginate(databasesListIndexes, {
123
167
  databaseId,
124
168
  collectionId,
125
- queries: ['limit(100)'],
126
169
  parseOutput: false
127
- });
170
+ }, 100, 'indexes');
128
171
 
129
- const readyIndexKeys = remoteIndexes.filter((index) => {
130
- if (indexKeys.includes(index.key)) {
131
- if (['stuck', 'failed'].includes(index.status)) {
132
- throw new Error(`Index '${index.key}' failed!`);
133
- }
172
+ const ready = indexes
173
+ .filter((index) => {
174
+ if (indexKeys.includes(index.key)) {
175
+ if (['stuck', 'failed'].includes(index.status)) {
176
+ throw new Error(`Index '${index.key}' failed!`);
177
+ }
134
178
 
135
- return index.status === 'available';
136
- }
179
+ return index.status === 'available';
180
+ }
137
181
 
138
- return false;
139
- }).map(index => index.key);
182
+ return false;
183
+ })
184
+ .map(index => index.key);
140
185
 
141
- if (readyIndexKeys.length >= indexKeys.length) {
186
+ if (ready.length >= indexKeys.length) {
142
187
  return true;
143
188
  }
144
189
 
145
190
  await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE));
146
- return await awaitPools.expectIndexes(databaseId, collectionId, indexKeys, iteration + 1);
191
+
192
+ return await awaitPools.expectIndexes(
193
+ databaseId,
194
+ collectionId,
195
+ indexKeys,
196
+ iteration + 1
197
+ );
147
198
  },
148
199
  }
149
200
 
@@ -249,19 +300,19 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
249
300
  if (func.variables) {
250
301
  // Delete existing variables
251
302
 
252
- // TODO: Pagination?
253
- const { variables: remoteVariables } = await functionsListVariables({
303
+ const { total } = await functionsListVariables({
254
304
  functionId: func['$id'],
255
- queries: ['limit(100)'],
305
+ queries: ['limit(1)'],
256
306
  parseOutput: false
257
307
  });
258
308
 
259
309
  let deployVariables = yes;
260
- if (remoteVariables.length == 0) {
310
+
311
+ if (total === 0) {
261
312
  deployVariables = true;
262
313
  } else if (remoteVariables.length > 0 && !yes) {
263
314
  const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1])
264
- deployVariables = variableAnswers.override === "YES";
315
+ deployVariables = variableAnswers.override.toLowerCase() === "yes";
265
316
  }
266
317
 
267
318
  if (!deployVariables) {
@@ -291,7 +342,7 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
291
342
 
292
343
  // Create tag
293
344
  if (!func.entrypoint) {
294
- answers = await inquirer.prompt(questionsGetEntrypoint)
345
+ const answers = await inquirer.prompt(questionsGetEntrypoint)
295
346
  func.entrypoint = answers.entrypoint;
296
347
  localConfig.updateFunction(func['$id'], func);
297
348
  }
@@ -473,22 +524,27 @@ const deployCollection = async ({ all, yes } = {}) => {
473
524
  databaseId: collection.databaseId,
474
525
  parseOutput: false,
475
526
  });
527
+
476
528
  databaseId = database.$id;
477
529
 
478
- await databasesUpdate({
479
- databaseId: collection.databaseId,
480
- name: localDatabase.name ?? collection.databaseId,
481
- parseOutput: false
482
- })
530
+ if (database.name !== (localDatabase.name ?? collection.databaseId)) {
531
+ await databasesUpdate({
532
+ databaseId: collection.databaseId,
533
+ name: localDatabase.name ?? collection.databaseId,
534
+ parseOutput: false
535
+ })
483
536
 
484
- success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`);
537
+ success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`);
538
+ }
485
539
  } catch (err) {
486
540
  log(`Database ${collection.databaseId} not found. Creating it now...`);
541
+
487
542
  const database = await databasesCreate({
488
543
  databaseId: collection.databaseId,
489
544
  name: localDatabase.name ?? collection.databaseId,
490
545
  parseOutput: false,
491
546
  });
547
+
492
548
  databaseId = database.$id;
493
549
  }
494
550
 
@@ -498,11 +554,12 @@ const deployCollection = async ({ all, yes } = {}) => {
498
554
  collectionId: collection['$id'],
499
555
  parseOutput: false,
500
556
  })
557
+
501
558
  log(`Collection ${collection.name} ( ${collection['$id']} ) already exists.`);
502
559
 
503
560
  if (!yes) {
504
- answers = await inquirer.prompt(questionsDeployCollections[1])
505
- if (answers.override !== "YES") {
561
+ const answers = await inquirer.prompt(questionsDeployCollections[1])
562
+ if (answers.override.toLowerCase() !== "yes") {
506
563
  log(`Received "${answers.override}". Skipping ${collection.name} ( ${collection['$id']} )`);
507
564
  continue;
508
565
  }
@@ -510,15 +567,13 @@ const deployCollection = async ({ all, yes } = {}) => {
510
567
 
511
568
  log(`Deleting indexes and attributes ... `);
512
569
 
513
- // TODO: Pagination?
514
- const { indexes: remoteIndexes } = await databasesListIndexes({
570
+ const { indexes } = await paginate(databasesListIndexes, {
515
571
  databaseId,
516
572
  collectionId: collection['$id'],
517
- queries: ['limit(100)'],
518
573
  parseOutput: false
519
- });
574
+ }, 100, 'indexes');
520
575
 
521
- await Promise.all(remoteIndexes.map(async index => {
576
+ await Promise.all(indexes.map(async index => {
522
577
  await databasesDeleteIndex({
523
578
  databaseId,
524
579
  collectionId: collection['$id'],
@@ -527,20 +582,18 @@ const deployCollection = async ({ all, yes } = {}) => {
527
582
  });
528
583
  }));
529
584
 
530
- const deleteIndexesPoolStatus = await awaitPools.wipeIndexes(databaseId, collection['$id']);
531
- if (!deleteIndexesPoolStatus) {
532
- throw new Error("Index deletion did not finish for too long.");
585
+ let result = await awaitPools.wipeIndexes(databaseId, collection['$id']);
586
+ if (!result) {
587
+ throw new Error("Index deletion timed out.");
533
588
  }
534
589
 
535
- // TODO: Pagination?
536
- const { attributes: remoteAttributes } = await databasesListAttributes({
590
+ const { attributes } = await paginate(databasesListAttributes, {
537
591
  databaseId,
538
592
  collectionId: collection['$id'],
539
- queries: ['limit(100)'],
540
593
  parseOutput: false
541
- });
594
+ }, 100, 'attributes');
542
595
 
543
- await Promise.all(remoteAttributes.map(async attribute => {
596
+ await Promise.all(attributes.map(async attribute => {
544
597
  await databasesDeleteAttribute({
545
598
  databaseId,
546
599
  collectionId: collection['$id'],
@@ -551,7 +604,7 @@ const deployCollection = async ({ all, yes } = {}) => {
551
604
 
552
605
  const deleteAttributesPoolStatus = await awaitPools.wipeAttributes(databaseId, collection['$id']);
553
606
  if (!deleteAttributesPoolStatus) {
554
- throw new Error("Attribute deletion did not finish for too long.");
607
+ throw new Error("Attribute deletion timed out.");
555
608
  }
556
609
 
557
610
  await databasesUpdateCollection({
@@ -581,20 +634,26 @@ const deployCollection = async ({ all, yes } = {}) => {
581
634
  }
582
635
 
583
636
  // Create all non-relationship attributes first
584
- const nonRelationshipAttributes = collection.attributes.filter(attribute => attribute.type !== 'relationship');
585
- await Promise.all(nonRelationshipAttributes.map(attribute => {
637
+ const attributes = collection.attributes.filter(attribute => attribute.type !== 'relationship');
638
+
639
+ await Promise.all(attributes.map(attribute => {
586
640
  return createAttribute(databaseId, collection['$id'], attribute);
587
641
  }));
588
642
 
589
- const nonRelationshipAttributeKeys = nonRelationshipAttributes.map(attribute => attribute.key);
590
- const createPoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], nonRelationshipAttributeKeys);
591
- if (!createPoolStatus) {
592
- throw new Error("Attribute creation did not finish for too long.");
643
+ let result = await awaitPools.expectAttributes(
644
+ databaseId,
645
+ collection['$id'],
646
+ attributes.map(attribute => attribute.key)
647
+ );
648
+
649
+ if (!result) {
650
+ throw new Error("Attribute creation timed out.");
593
651
  }
594
652
 
595
- success(`Created ${nonRelationshipAttributeKeys.length} non-relationship attributes`);
653
+ success(`Created ${attributes.length} non-relationship attributes`);
596
654
 
597
655
  log(`Creating indexes ...`)
656
+
598
657
  await Promise.all(collection.indexes.map(async index => {
599
658
  await databasesCreateIndex({
600
659
  databaseId,
@@ -607,10 +666,14 @@ const deployCollection = async ({ all, yes } = {}) => {
607
666
  });
608
667
  }));
609
668
 
610
- const indexKeys = collection.indexes.map(attribute => attribute.key);
611
- const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys);
612
- if (!indexPoolStatus) {
613
- throw new Error("Index creation did not finish for too long.");
669
+ result = await awaitPools.expectIndexes(
670
+ databaseId,
671
+ collection['$id'],
672
+ collection.indexes.map(attribute => attribute.key)
673
+ );
674
+
675
+ if (!result) {
676
+ throw new Error("Index creation timed out.");
614
677
  }
615
678
 
616
679
  success(`Created ${collection.indexes.length} indexes`);
@@ -620,23 +683,31 @@ const deployCollection = async ({ all, yes } = {}) => {
620
683
 
621
684
  // Create the relationship attributes
622
685
  for (let collection of collections) {
623
- const relationshipAttributes = collection.attributes.filter(attribute => attribute.type === 'relationship' && attribute.side === 'parent');
686
+ const relationships = collection.attributes.filter(attribute =>
687
+ attribute.type === 'relationship' && attribute.side === 'parent'
688
+ );
624
689
 
625
- if (relationshipAttributes.length === 0) continue;
690
+ if (relationships.length === 0) {
691
+ continue;
692
+ }
626
693
 
627
694
  log(`Deploying relationships for collection ${collection.name} ( ${collection['$id']} )`);
628
695
 
629
- await Promise.all(relationshipAttributes.map(attribute => {
696
+ await Promise.all(relationships.map(attribute => {
630
697
  return createAttribute(collection['databaseId'], collection['$id'], attribute);
631
698
  }));
632
699
 
633
- const nonRelationshipAttributeKeys = relationshipAttributes.map(attribute => attribute.key);
634
- const createPoolStatus = await awaitPools.expectAttributes(collection['databaseId'], collection['$id'], nonRelationshipAttributeKeys);
635
- if (!createPoolStatus) {
636
- throw new Error("Attribute creation did not finish for too long.");
700
+ let result = await awaitPools.expectAttributes(
701
+ collection['databaseId'],
702
+ collection['$id'],
703
+ relationships.map(attribute => attribute.key)
704
+ );
705
+
706
+ if (!result) {
707
+ throw new Error("Attribute creation timed out.");
637
708
  }
638
709
 
639
- success(`Created ${nonRelationshipAttributeKeys.length} relationship attributes`);
710
+ success(`Created ${relationships.length} relationship attributes`);
640
711
  }
641
712
  }
642
713
 
@@ -654,7 +725,7 @@ const deployBucket = async ({ all, yes } = {}) => {
654
725
  }
655
726
 
656
727
  if (bucketIds.length === 0) {
657
- let answers = await inquirer.prompt(questionsDeployBuckets[0])
728
+ const answers = await inquirer.prompt(questionsDeployBuckets[0])
658
729
  bucketIds.push(...answers.buckets);
659
730
  }
660
731
 
@@ -676,8 +747,8 @@ const deployBucket = async ({ all, yes } = {}) => {
676
747
  log(`Bucket ${bucket.name} ( ${bucket['$id']} ) already exists.`);
677
748
 
678
749
  if (!yes) {
679
- answers = await inquirer.prompt(questionsDeployBuckets[1])
680
- if (answers.override !== "YES") {
750
+ const answers = await inquirer.prompt(questionsDeployBuckets[1])
751
+ if (answers.override.toLowerCase() !== "yes") {
681
752
  log(`Received "${answers.override}". Skipping ${bucket.name} ( ${bucket['$id']} )`);
682
753
  continue;
683
754
  }
@@ -741,7 +812,7 @@ const deployTeam = async ({ all, yes } = {}) => {
741
812
  }
742
813
 
743
814
  if (teamIds.length === 0) {
744
- let answers = await inquirer.prompt(questionsDeployTeams[0])
815
+ const answers = await inquirer.prompt(questionsDeployTeams[0])
745
816
  teamIds.push(...answers.teams);
746
817
  }
747
818
 
@@ -763,8 +834,8 @@ const deployTeam = async ({ all, yes } = {}) => {
763
834
  log(`Team ${team.name} ( ${team['$id']} ) already exists.`);
764
835
 
765
836
  if (!yes) {
766
- answers = await inquirer.prompt(questionsDeployTeams[1])
767
- if (answers.override !== "YES") {
837
+ const answers = await inquirer.prompt(questionsDeployTeams[1])
838
+ if (answers.override.toLowerCase() !== "yes") {
768
839
  log(`Received "${answers.override}". Skipping ${team.name} ( ${team['$id']} )`);
769
840
  continue;
770
841
  }
@@ -808,7 +879,7 @@ deploy
808
879
  deploy
809
880
  .command("collection")
810
881
  .description("Deploy collections in the current project.")
811
- .option(`--all`, `Flag to deploy all functions`)
882
+ .option(`--all`, `Flag to deploy all collections`)
812
883
  .option(`--yes`, `Flag to confirm all warnings`)
813
884
  .action(actionRunner(deployCollection));
814
885
 
@@ -866,7 +866,7 @@ functions
866
866
 
867
867
  functions
868
868
  .command(`create`)
869
- .description(`Create a new function. You can pass a list of [permissions](/docs/permissions) to allow different project users or team with access to execute the function using the client API.`)
869
+ .description(`Create a new function. You can pass a list of [permissions](https://appwrite.io/docs/permissions) to allow different project users or team with access to execute the function using the client API.`)
870
870
  .requiredOption(`--functionId <functionId>`, `Function ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`)
871
871
  .requiredOption(`--name <name>`, `Function name. Max length: 128 chars.`)
872
872
  .requiredOption(`--runtime <runtime>`, `Execution runtime.`)
@@ -943,7 +943,7 @@ functions
943
943
 
944
944
  functions
945
945
  .command(`createDeployment`)
946
- .description(`Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID. This endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](/docs/functions). Use the "command" param to set the entrypoint used to execute your code.`)
946
+ .description(`Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID. This endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https://appwrite.io/docs/functions). Use the "command" param to set the entrypoint used to execute your code.`)
947
947
  .requiredOption(`--functionId <functionId>`, `Function ID.`)
948
948
  .requiredOption(`--code <code>`, `Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.`)
949
949
  .requiredOption(`--activate <activate>`, `Automatically activate the deployment when it is finished building.`, parseBool)
@@ -982,7 +982,7 @@ functions
982
982
 
983
983
  functions
984
984
  .command(`downloadDeployment`)
985
- .description(``)
985
+ .description(`Get a Deployment's contents by its unique ID. This endpoint supports range requests for partial or streaming file download.`)
986
986
  .requiredOption(`--functionId <functionId>`, `Function ID.`)
987
987
  .requiredOption(`--deploymentId <deploymentId>`, `Deployment ID.`)
988
988
  .requiredOption(`--destination <path>`, `output file path.`)