appwrite-cli 5.0.5 → 6.0.0-rc.1

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.
Files changed (53) hide show
  1. package/README.md +4 -4
  2. package/docs/examples/functions/create-build.md +1 -1
  3. package/docs/examples/functions/create-execution.md +1 -0
  4. package/docs/examples/functions/create.md +1 -0
  5. package/docs/examples/functions/delete-execution.md +3 -0
  6. package/docs/examples/functions/update-deployment-build.md +3 -0
  7. package/docs/examples/functions/update.md +1 -0
  8. package/docs/examples/projects/create-j-w-t.md +4 -0
  9. package/docs/examples/projects/update-mock-numbers.md +3 -0
  10. package/docs/examples/projects/update-session-alerts.md +3 -0
  11. package/docs/examples/users/create-j-w-t.md +4 -0
  12. package/docs/examples/vcs/get-repository-contents.md +4 -0
  13. package/index.js +34 -7
  14. package/install.ps1 +3 -3
  15. package/install.sh +2 -2
  16. package/lib/client.js +17 -3
  17. package/lib/commands/account.js +306 -152
  18. package/lib/commands/assistant.js +8 -5
  19. package/lib/commands/avatars.js +114 -58
  20. package/lib/commands/console.js +8 -5
  21. package/lib/commands/databases.js +353 -164
  22. package/lib/commands/functions.js +310 -100
  23. package/lib/commands/generic.js +206 -54
  24. package/lib/commands/graphql.js +14 -8
  25. package/lib/commands/health.js +140 -71
  26. package/lib/commands/init.js +250 -155
  27. package/lib/commands/locale.js +50 -26
  28. package/lib/commands/messaging.js +334 -156
  29. package/lib/commands/migrations.js +98 -50
  30. package/lib/commands/project.js +38 -20
  31. package/lib/commands/projects.js +449 -144
  32. package/lib/commands/proxy.js +32 -17
  33. package/lib/commands/pull.js +231 -0
  34. package/lib/commands/push.js +1518 -0
  35. package/lib/commands/run.js +282 -0
  36. package/lib/commands/storage.js +160 -76
  37. package/lib/commands/teams.js +102 -50
  38. package/lib/commands/users.js +324 -134
  39. package/lib/commands/vcs.js +102 -29
  40. package/lib/config.js +190 -18
  41. package/lib/emulation/docker.js +187 -0
  42. package/lib/emulation/utils.js +177 -0
  43. package/lib/id.js +30 -0
  44. package/lib/paginate.js +1 -2
  45. package/lib/parser.js +69 -12
  46. package/lib/questions.js +452 -80
  47. package/lib/sdks.js +1 -1
  48. package/lib/spinner.js +103 -0
  49. package/lib/utils.js +242 -4
  50. package/lib/validations.js +17 -0
  51. package/package.json +6 -2
  52. package/scoop/appwrite.json +3 -3
  53. package/lib/commands/deploy.js +0 -940
@@ -0,0 +1,1518 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require("inquirer");
3
+ const JSONbig = require("json-bigint")({ storeAsString: false });
4
+ const { Command } = require("commander");
5
+ const { localConfig, globalConfig } = require("../config");
6
+ const { Spinner, SPINNER_ARC, SPINNER_DOTS } = require('../spinner');
7
+ const { paginate } = require('../paginate');
8
+ const { questionsPushBuckets, questionsPushTeams, questionsPushFunctions, questionsGetEntrypoint, questionsPushCollections, questionsConfirmPushCollections, questionsPushMessagingTopics, questionsPushResources } = require("../questions");
9
+ const { cliConfig, actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
10
+ const { proxyListRules } = require('./proxy');
11
+ const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsGetDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions');
12
+ const {
13
+ databasesGet,
14
+ databasesCreate,
15
+ databasesUpdate,
16
+ databasesCreateBooleanAttribute,
17
+ databasesGetCollection,
18
+ databasesCreateCollection,
19
+ databasesCreateStringAttribute,
20
+ databasesCreateIntegerAttribute,
21
+ databasesCreateFloatAttribute,
22
+ databasesCreateEmailAttribute,
23
+ databasesCreateDatetimeAttribute,
24
+ databasesCreateIndex,
25
+ databasesCreateUrlAttribute,
26
+ databasesCreateIpAttribute,
27
+ databasesCreateEnumAttribute,
28
+ databasesUpdateBooleanAttribute,
29
+ databasesUpdateStringAttribute,
30
+ databasesUpdateIntegerAttribute,
31
+ databasesUpdateFloatAttribute,
32
+ databasesUpdateEmailAttribute,
33
+ databasesUpdateDatetimeAttribute,
34
+ databasesUpdateUrlAttribute,
35
+ databasesUpdateIpAttribute,
36
+ databasesUpdateEnumAttribute,
37
+ databasesUpdateRelationshipAttribute,
38
+ databasesCreateRelationshipAttribute,
39
+ databasesDeleteAttribute,
40
+ databasesListAttributes,
41
+ databasesListIndexes,
42
+ databasesUpdateCollection
43
+ } = require("./databases");
44
+ const {
45
+ storageGetBucket, storageUpdateBucket, storageCreateBucket
46
+ } = require("./storage");
47
+ const {
48
+ messagingGetTopic, messagingUpdateTopic, messagingCreateTopic
49
+ } = require("./messaging");
50
+ const {
51
+ teamsGet,
52
+ teamsUpdateName,
53
+ teamsCreate
54
+ } = require("./teams");
55
+ const {
56
+ projectsUpdate,
57
+ projectsUpdateServiceStatus,
58
+ projectsUpdateAuthStatus,
59
+ projectsUpdateAuthDuration,
60
+ projectsUpdateAuthLimit,
61
+ projectsUpdateAuthSessionsLimit,
62
+ projectsUpdateAuthPasswordDictionary,
63
+ projectsUpdateAuthPasswordHistory,
64
+ projectsUpdatePersonalDataCheck,
65
+ } = require("./projects");
66
+ const { checkDeployConditions } = require('../utils');
67
+
68
+ const STEP_SIZE = 100; // Resources
69
+ const POLL_DEBOUNCE = 2000; // Milliseconds
70
+ const POLL_MAX_DEBOUNCE = 30; // Times
71
+
72
+ let pollMaxDebounces = 30;
73
+
74
+ const changeableKeys = ['status', 'required', 'xdefault', 'elements', 'min', 'max', 'default', 'error'];
75
+
76
+ const awaitPools = {
77
+ wipeAttributes: async (databaseId, collectionId, iteration = 1) => {
78
+ if (iteration > pollMaxDebounces) {
79
+ return false;
80
+ }
81
+
82
+ const { total } = await databasesListAttributes({
83
+ databaseId,
84
+ collectionId,
85
+ queries: [JSON.stringify({ method: 'limit', values: [1] })],
86
+ parseOutput: false
87
+ });
88
+
89
+ if (total === 0) {
90
+ return true;
91
+ }
92
+
93
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
94
+ if (steps > 1 && iteration === 1) {
95
+ pollMaxDebounces *= steps;
96
+
97
+ log('Found a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
98
+ }
99
+
100
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
101
+
102
+ return await awaitPools.wipeAttributes(
103
+ databaseId,
104
+ collectionId,
105
+ iteration + 1
106
+ );
107
+ },
108
+ wipeIndexes: async (databaseId, collectionId, iteration = 1) => {
109
+ if (iteration > pollMaxDebounces) {
110
+ return false;
111
+ }
112
+
113
+ const { total } = await databasesListIndexes({
114
+ databaseId,
115
+ collectionId,
116
+ queries: [JSON.stringify({ method: 'limit', values: [1] })],
117
+ parseOutput: false
118
+ });
119
+
120
+ if (total === 0) {
121
+ return true;
122
+ }
123
+
124
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
125
+ if (steps > 1 && iteration === 1) {
126
+ pollMaxDebounces *= steps;
127
+
128
+ log('Found a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
129
+ }
130
+
131
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
132
+
133
+ return await awaitPools.wipeIndexes(
134
+ databaseId,
135
+ collectionId,
136
+ iteration + 1
137
+ );
138
+ },
139
+ wipeVariables: async (functionId, iteration = 1) => {
140
+ if (iteration > pollMaxDebounces) {
141
+ return false;
142
+ }
143
+
144
+ const { total } = await functionsListVariables({
145
+ functionId,
146
+ queries: ['limit(1)'],
147
+ parseOutput: false
148
+ });
149
+
150
+ if (total === 0) {
151
+ return true;
152
+ }
153
+
154
+ let steps = Math.max(1, Math.ceil(total / STEP_SIZE));
155
+ if (steps > 1 && iteration === 1) {
156
+ pollMaxDebounces *= steps;
157
+
158
+ log('Found a large number of variables, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
159
+ }
160
+
161
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
162
+
163
+ return await awaitPools.wipeVariables(
164
+ functionId,
165
+ iteration + 1
166
+ );
167
+ },
168
+ deleteAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
169
+ if (iteration > pollMaxDebounces) {
170
+ return false;
171
+ }
172
+
173
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
174
+ if (steps > 1 && iteration === 1) {
175
+ pollMaxDebounces *= steps;
176
+
177
+ log('Found a large number of attributes to be deleted. Increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
178
+ }
179
+
180
+ const { attributes } = await paginate(databasesListAttributes, {
181
+ databaseId,
182
+ collectionId,
183
+ parseOutput: false
184
+ }, 100, 'attributes');
185
+
186
+ const ready = attributeKeys.filter(attribute => attributes.includes(attribute.key));
187
+
188
+ if (ready.length === 0) {
189
+ return true;
190
+ }
191
+
192
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
193
+
194
+ return await awaitPools.expectAttributes(
195
+ databaseId,
196
+ collectionId,
197
+ attributeKeys,
198
+ iteration + 1
199
+ );
200
+ },
201
+ expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => {
202
+ if (iteration > pollMaxDebounces) {
203
+ return false;
204
+ }
205
+
206
+ let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE));
207
+ if (steps > 1 && iteration === 1) {
208
+ pollMaxDebounces *= steps;
209
+
210
+ log('Creating a large number of attributes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
211
+ }
212
+
213
+ const { attributes } = await paginate(databasesListAttributes, {
214
+ databaseId,
215
+ collectionId,
216
+ parseOutput: false
217
+ }, 100, 'attributes');
218
+
219
+ const ready = attributes
220
+ .filter(attribute => {
221
+ if (attributeKeys.includes(attribute.key)) {
222
+ if (['stuck', 'failed'].includes(attribute.status)) {
223
+ throw new Error(`Attribute '${attribute.key}' failed!`);
224
+ }
225
+
226
+ return attribute.status === 'available';
227
+ }
228
+
229
+ return false;
230
+ })
231
+ .map(attribute => attribute.key);
232
+
233
+ if (ready.length === attributeKeys.length) {
234
+ return true;
235
+ }
236
+
237
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
238
+
239
+ return await awaitPools.expectAttributes(
240
+ databaseId,
241
+ collectionId,
242
+ attributeKeys,
243
+ iteration + 1
244
+ );
245
+ },
246
+ expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => {
247
+ if (iteration > pollMaxDebounces) {
248
+ return false;
249
+ }
250
+
251
+ let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE));
252
+ if (steps > 1 && iteration === 1) {
253
+ pollMaxDebounces *= steps;
254
+
255
+ log('Creating a large number of indexes, increasing timeout to ' + (pollMaxDebounces * POLL_DEBOUNCE / 1000 / 60) + ' minutes')
256
+ }
257
+
258
+ const { indexes } = await paginate(databasesListIndexes, {
259
+ databaseId,
260
+ collectionId,
261
+ parseOutput: false
262
+ }, 100, 'indexes');
263
+
264
+ const ready = indexes
265
+ .filter((index) => {
266
+ if (indexKeys.includes(index.key)) {
267
+ if (['stuck', 'failed'].includes(index.status)) {
268
+ throw new Error(`Index '${index.key}' failed!`);
269
+ }
270
+
271
+ return index.status === 'available';
272
+ }
273
+
274
+ return false;
275
+ })
276
+ .map(index => index.key);
277
+
278
+ if (ready.length >= indexKeys.length) {
279
+ return true;
280
+ }
281
+
282
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
283
+
284
+ return await awaitPools.expectIndexes(
285
+ databaseId,
286
+ collectionId,
287
+ indexKeys,
288
+ iteration + 1
289
+ );
290
+ },
291
+ }
292
+
293
+ const createAttribute = async (databaseId, collectionId, attribute) => {
294
+ switch (attribute.type) {
295
+ case 'string':
296
+ switch (attribute.format) {
297
+ case 'email':
298
+ return await databasesCreateEmailAttribute({
299
+ databaseId,
300
+ collectionId,
301
+ key: attribute.key,
302
+ required: attribute.required,
303
+ xdefault: attribute.default,
304
+ array: attribute.array,
305
+ parseOutput: false
306
+ })
307
+ case 'url':
308
+ return await databasesCreateUrlAttribute({
309
+ databaseId,
310
+ collectionId,
311
+ key: attribute.key,
312
+ required: attribute.required,
313
+ xdefault: attribute.default,
314
+ array: attribute.array,
315
+ parseOutput: false
316
+ })
317
+ case 'ip':
318
+ return await databasesCreateIpAttribute({
319
+ databaseId,
320
+ collectionId,
321
+ key: attribute.key,
322
+ required: attribute.required,
323
+ xdefault: attribute.default,
324
+ array: attribute.array,
325
+ parseOutput: false
326
+ })
327
+ case 'enum':
328
+ return await databasesCreateEnumAttribute({
329
+ databaseId,
330
+ collectionId,
331
+ key: attribute.key,
332
+ elements: attribute.elements,
333
+ required: attribute.required,
334
+ xdefault: attribute.default,
335
+ array: attribute.array,
336
+ parseOutput: false
337
+ })
338
+ default:
339
+ return await databasesCreateStringAttribute({
340
+ databaseId,
341
+ collectionId,
342
+ key: attribute.key,
343
+ size: attribute.size,
344
+ required: attribute.required,
345
+ xdefault: attribute.default,
346
+ array: attribute.array,
347
+ parseOutput: false
348
+ })
349
+
350
+ }
351
+ case 'integer':
352
+ return await databasesCreateIntegerAttribute({
353
+ databaseId,
354
+ collectionId,
355
+ key: attribute.key,
356
+ required: attribute.required,
357
+ min: parseInt(attribute.min.toString()),
358
+ max: parseInt(attribute.max.toString()),
359
+ xdefault: attribute.default,
360
+ array: attribute.array,
361
+ parseOutput: false
362
+ })
363
+ case 'double':
364
+ return databasesCreateFloatAttribute({
365
+ databaseId,
366
+ collectionId,
367
+ key: attribute.key,
368
+ required: attribute.required,
369
+ min: parseFloat(attribute.min.toString()),
370
+ max: parseFloat(attribute.max.toString()),
371
+ xdefault: attribute.default,
372
+ array: attribute.array,
373
+ parseOutput: false
374
+ })
375
+ case 'boolean':
376
+ return databasesCreateBooleanAttribute({
377
+ databaseId,
378
+ collectionId,
379
+ key: attribute.key,
380
+ required: attribute.required,
381
+ xdefault: attribute.default,
382
+ array: attribute.array,
383
+ parseOutput: false
384
+ })
385
+ case 'datetime':
386
+ return databasesCreateDatetimeAttribute({
387
+ databaseId,
388
+ collectionId,
389
+ key: attribute.key,
390
+ required: attribute.required,
391
+ xdefault: attribute.default,
392
+ array: attribute.array,
393
+ parseOutput: false
394
+ })
395
+ case 'relationship':
396
+ return databasesCreateRelationshipAttribute({
397
+ databaseId,
398
+ collectionId,
399
+ relatedCollectionId: attribute.relatedCollection,
400
+ type: attribute.relationType,
401
+ twoWay: attribute.twoWay,
402
+ key: attribute.key,
403
+ twoWayKey: attribute.twoWayKey,
404
+ onDelete: attribute.onDelete,
405
+ parseOutput: false
406
+ })
407
+ }
408
+ }
409
+
410
+ const updateAttribute = async (databaseId, collectionId, attribute) => {
411
+ switch (attribute.type) {
412
+ case 'string':
413
+ switch (attribute.format) {
414
+ case 'email':
415
+ return await databasesUpdateEmailAttribute({
416
+ databaseId,
417
+ collectionId,
418
+ key: attribute.key,
419
+ required: attribute.required,
420
+ xdefault: attribute.default,
421
+ array: attribute.array,
422
+ parseOutput: false
423
+ })
424
+ case 'url':
425
+ return await databasesUpdateUrlAttribute({
426
+ databaseId,
427
+ collectionId,
428
+ key: attribute.key,
429
+ required: attribute.required,
430
+ xdefault: attribute.default,
431
+ array: attribute.array,
432
+ parseOutput: false
433
+ })
434
+ case 'ip':
435
+ return await databasesUpdateIpAttribute({
436
+ databaseId,
437
+ collectionId,
438
+ key: attribute.key,
439
+ required: attribute.required,
440
+ xdefault: attribute.default,
441
+ array: attribute.array,
442
+ parseOutput: false
443
+ })
444
+ case 'enum':
445
+ return await databasesUpdateEnumAttribute({
446
+ databaseId,
447
+ collectionId,
448
+ key: attribute.key,
449
+ elements: attribute.elements,
450
+ required: attribute.required,
451
+ xdefault: attribute.default,
452
+ array: attribute.array,
453
+ parseOutput: false
454
+ })
455
+ default:
456
+ return await databasesUpdateStringAttribute({
457
+ databaseId,
458
+ collectionId,
459
+ key: attribute.key,
460
+ size: attribute.size,
461
+ required: attribute.required,
462
+ xdefault: attribute.default,
463
+ array: attribute.array,
464
+ parseOutput: false
465
+ })
466
+
467
+ }
468
+ case 'integer':
469
+ return await databasesUpdateIntegerAttribute({
470
+ databaseId,
471
+ collectionId,
472
+ key: attribute.key,
473
+ required: attribute.required,
474
+ min: parseInt(attribute.min.toString()),
475
+ max: parseInt(attribute.max.toString()),
476
+ xdefault: attribute.default,
477
+ array: attribute.array,
478
+ parseOutput: false
479
+ })
480
+ case 'double':
481
+ return databasesUpdateFloatAttribute({
482
+ databaseId,
483
+ collectionId,
484
+ key: attribute.key,
485
+ required: attribute.required,
486
+ min: parseFloat(attribute.min.toString()),
487
+ max: parseFloat(attribute.max.toString()),
488
+ xdefault: attribute.default,
489
+ array: attribute.array,
490
+ parseOutput: false
491
+ })
492
+ case 'boolean':
493
+ return databasesUpdateBooleanAttribute({
494
+ databaseId,
495
+ collectionId,
496
+ key: attribute.key,
497
+ required: attribute.required,
498
+ xdefault: attribute.default,
499
+ array: attribute.array,
500
+ parseOutput: false
501
+ })
502
+ case 'datetime':
503
+ return databasesUpdateDatetimeAttribute({
504
+ databaseId,
505
+ collectionId,
506
+ key: attribute.key,
507
+ required: attribute.required,
508
+ xdefault: attribute.default,
509
+ array: attribute.array,
510
+ parseOutput: false
511
+ })
512
+ case 'relationship':
513
+ return databasesUpdateRelationshipAttribute({
514
+ databaseId,
515
+ collectionId,
516
+ relatedCollectionId: attribute.relatedCollection,
517
+ type: attribute.relationType,
518
+ twoWay: attribute.twoWay,
519
+ key: attribute.key,
520
+ twoWayKey: attribute.twoWayKey,
521
+ onDelete: attribute.onDelete,
522
+ parseOutput: false
523
+ })
524
+ }
525
+ }
526
+ const deleteAttribute = async (collection, attribute) => {
527
+ log(`Deleting attribute ${attribute.key} of ${collection.name} ( ${collection['$id']} )`);
528
+
529
+ await databasesDeleteAttribute({
530
+ databaseId: collection['databaseId'],
531
+ collectionId: collection['$id'],
532
+ key: attribute.key,
533
+ parseOutput: false
534
+ });
535
+ }
536
+
537
+
538
+ /**
539
+ * Check if attribute non-changeable fields has been changed
540
+ * If so return the differences as an object.
541
+ * @param remote
542
+ * @param local
543
+ * @param collection
544
+ * @param recraeting when true will check only non-changeable keys
545
+ * @returns {undefined|{reason: string, action: *, attribute, key: string}}
546
+ */
547
+ const checkAttributeChanges = (remote, local, collection, recraeting = true) => {
548
+ if (local === undefined) {
549
+ return undefined;
550
+ }
551
+
552
+ const keyName = `${chalk.yellow(local.key)} in ${collection.name} (${collection['$id']})`;
553
+ const action = chalk.cyan(recraeting ? 'recreating' : 'changing');
554
+ let reason = '';
555
+ let attribute = remote;
556
+
557
+ for (let key of Object.keys(remote)) {
558
+ if (changeableKeys.includes(key)) {
559
+ if (!recraeting) {
560
+ if (remote[key] !== local[key]) {
561
+ const bol = reason === '' ? '' : '\n';
562
+ reason += `${bol}${key} changed from ${chalk.red(remote[key])} to ${chalk.green(local[key])}`;
563
+ attribute = local;
564
+ }
565
+ }
566
+ continue;
567
+ }
568
+
569
+ if (!recraeting) {
570
+ continue;
571
+ }
572
+
573
+ if (remote[key] !== local[key]) {
574
+ const bol = reason === '' ? '' : '\n';
575
+ reason += `${bol}${key} changed from ${chalk.red(remote[key])} to ${chalk.green(local[key])}`;
576
+ }
577
+ }
578
+
579
+ return reason === '' ? undefined : { key: keyName, attribute, reason, action };
580
+ }
581
+
582
+ /**
583
+ * Check if attributes contain the given attribute
584
+ * @param attribute
585
+ * @param attributes
586
+ * @returns {*}
587
+ */
588
+ const attributesContains = (attribute, attributes) => attributes.find((attr) => attr.key === attribute.key);
589
+ const generateChangesObject = (attribute, collection, isAdding) => {
590
+ return {
591
+ key: `${chalk.yellow(attribute.key)} in ${collection.name} (${collection['$id']})`,
592
+ attribute: attribute,
593
+ reason: isAdding ? 'Field doesn\'t exist on the remote server' : 'Field doesn\'t exist in appwrite.json file',
594
+ action: isAdding ? chalk.green('adding') : chalk.red('deleting')
595
+ };
596
+
597
+ };
598
+
599
+ /**
600
+ * Filter deleted and recreated attributes,
601
+ * return list of attributes to create
602
+ * @param remoteAttributes
603
+ * @param localAttributes
604
+ * @param collection
605
+ * @returns {Promise<*|*[]>}
606
+ */
607
+ const attributesToCreate = async (remoteAttributes, localAttributes, collection) => {
608
+
609
+ const deleting = remoteAttributes.filter((attribute) => !attributesContains(attribute, localAttributes)).map((attr) => generateChangesObject(attr, collection, false));
610
+ const adding = localAttributes.filter((attribute) => !attributesContains(attribute, remoteAttributes)).map((attr) => generateChangesObject(attr, collection, true));
611
+ const conflicts = remoteAttributes.map((attribute) => checkAttributeChanges(attribute, attributesContains(attribute, localAttributes), collection)).filter(attribute => attribute !== undefined);
612
+ const changes = remoteAttributes.map((attribute) => checkAttributeChanges(attribute, attributesContains(attribute, localAttributes), collection, false))
613
+ .filter(attribute => attribute !== undefined)
614
+ .filter(attribute => conflicts.filter(attr => attribute.key === attr.key).length !== 1);
615
+
616
+ let changedAttributes = [];
617
+ const changing = [...deleting, ...adding, ...conflicts, ...changes]
618
+ if (changing.length === 0) {
619
+ return changedAttributes;
620
+ }
621
+
622
+ log(!cliConfig.force ? 'There are pending changes in your collection deployment' : 'List of applied changes');
623
+
624
+ drawTable(changing.map((change) => {
625
+ return { Key: change.key, Action: change.action, Reason: change.reason, };
626
+ }));
627
+
628
+ if (!cliConfig.force) {
629
+ if (deleting.length > 0) {
630
+ log(`Attribute deletion will cause ${chalk.red('loss of data')}`);
631
+ }
632
+ if (conflicts.length > 0) {
633
+ log(`Attribute recreation will cause ${chalk.red('loss of data')}`);
634
+ }
635
+
636
+ const answers = await inquirer.prompt(questionsPushCollections[1]);
637
+
638
+ if (answers.changes.toLowerCase() !== 'yes') {
639
+ return changedAttributes;
640
+ }
641
+ }
642
+
643
+ if (conflicts.length > 0) {
644
+ changedAttributes = conflicts.map((change) => change.attribute);
645
+ await Promise.all(changedAttributes.map((changed) => deleteAttribute(collection, changed)));
646
+ remoteAttributes = remoteAttributes.filter((attribute) => !attributesContains(attribute, changedAttributes))
647
+ }
648
+
649
+ if (changes.length > 0) {
650
+ changedAttributes = changes.map((change) => change.attribute);
651
+ await Promise.all(changedAttributes.map((changed) => updateAttribute(collection['databaseId'],collection['$id'], changed)));
652
+ }
653
+
654
+ const deletingAttributes = deleting.map((change) => change.attribute);
655
+ await Promise.all(deletingAttributes.map((attribute) => deleteAttribute(collection, attribute)));
656
+ const attributeKeys = [...remoteAttributes.map(attribute => attribute.key), ...deletingAttributes.map(attribute => attribute.key)]
657
+
658
+ if (attributeKeys.length) {
659
+ const deleteAttributesPoolStatus = await awaitPools.deleteAttributes(collection['databaseId'], collection['$id'], attributeKeys);
660
+
661
+ if (!deleteAttributesPoolStatus) {
662
+ throw new Error("Attribute deletion timed out.");
663
+ }
664
+ }
665
+
666
+ return localAttributes.filter((attribute) => !attributesContains(attribute, remoteAttributes));
667
+ }
668
+ const createIndexes = async (indexes, collection) => {
669
+ log(`Creating indexes ...`)
670
+
671
+ for (let index of indexes) {
672
+ await databasesCreateIndex({
673
+ databaseId: collection['databaseId'],
674
+ collectionId: collection['$id'],
675
+ key: index.key,
676
+ type: index.type,
677
+ attributes: index.attributes,
678
+ orders: index.orders,
679
+ parseOutput: false
680
+ });
681
+ }
682
+
683
+ const result = await awaitPools.expectIndexes(
684
+ collection['databaseId'],
685
+ collection['$id'],
686
+ indexes.map(index => index.key)
687
+ );
688
+
689
+ if (!result) {
690
+ throw new Error("Index creation timed out.");
691
+ }
692
+
693
+ success(`Created ${indexes.length} indexes`);
694
+ }
695
+ const createAttributes = async (attributes, collection) => {
696
+ for (let attribute of attributes) {
697
+ if (attribute.side !== 'child') {
698
+ await createAttribute(collection['databaseId'], collection['$id'], attribute);
699
+ }
700
+ }
701
+
702
+ const result = await awaitPools.expectAttributes(
703
+ collection['databaseId'],
704
+ collection['$id'],
705
+ collection.attributes.map(attribute => attribute.key)
706
+ );
707
+
708
+ if (!result) {
709
+ throw new Error(`Attribute creation timed out.`);
710
+ }
711
+
712
+ success(`Created ${attributes.length} attributes`);
713
+ }
714
+
715
+ const pushResources = async () => {
716
+ const actions = {
717
+ project: pushProject,
718
+ functions: pushFunction,
719
+ collections: pushCollection,
720
+ buckets: pushBucket,
721
+ teams: pushTeam,
722
+ messages: pushMessagingTopic
723
+ }
724
+
725
+ if (cliConfig.all) {
726
+ for (let action of Object.values(actions)) {
727
+ await action({ returnOnZero: true });
728
+ }
729
+ } else {
730
+ const answers = await inquirer.prompt(questionsPushResources[0]);
731
+
732
+ const action = actions[answers.resource];
733
+ if (action !== undefined) {
734
+ await action({ returnOnZero: true });
735
+ }
736
+ }
737
+ };
738
+
739
+ const pushProject = async () => {
740
+ try {
741
+ const projectId = localConfig.getProject().projectId;
742
+ const projectName = localConfig.getProject().projectName;
743
+ const settings = localConfig.getProject().projectSettings ?? {};
744
+
745
+ log(`Updating project ${projectId}`);
746
+
747
+ if (projectName) {
748
+ await projectsUpdate({
749
+ projectId,
750
+ name: projectName,
751
+ parseOutput: false
752
+ });
753
+ }
754
+
755
+ if (settings.services) {
756
+ log('Updating service statuses');
757
+ for (let [service, status] of Object.entries(settings.services)) {
758
+ await projectsUpdateServiceStatus({
759
+ projectId,
760
+ service,
761
+ status,
762
+ parseOutput: false
763
+ });
764
+ }
765
+ }
766
+
767
+ if (settings.auth) {
768
+ if (settings.auth.security) {
769
+ log('Updating auth security settings');
770
+ await projectsUpdateAuthDuration({ projectId, duration: settings.auth.security.duration, parseOutput: false });
771
+ await projectsUpdateAuthLimit({ projectId, limit: settings.auth.security.limit, parseOutput: false });
772
+ await projectsUpdateAuthSessionsLimit({ projectId, limit: settings.auth.security.sessionsLimit, parseOutput: false });
773
+ await projectsUpdateAuthPasswordDictionary({ projectId, enabled: settings.auth.security.passwordDictionary, parseOutput: false });
774
+ await projectsUpdateAuthPasswordHistory({ projectId, limit: settings.auth.security.passwordHistory, parseOutput: false });
775
+ await projectsUpdatePersonalDataCheck({ projectId, enabled: settings.auth.security.personalDataCheck, parseOutput: false });
776
+ }
777
+
778
+ if (settings.auth.methods) {
779
+ log('Updating auth login methods');
780
+
781
+ for (let [method, status] of Object.entries(settings.auth.methods)) {
782
+ await projectsUpdateAuthStatus({
783
+ projectId,
784
+ method,
785
+ status,
786
+ parseOutput: false
787
+ });
788
+ }
789
+ }
790
+ }
791
+
792
+ success("Project configuration updated.");
793
+ } catch (e) {
794
+ throw e;
795
+ }
796
+ }
797
+
798
+ const pushFunction = async ({ functionId, async, returnOnZero } = { returnOnZero: false }) => {
799
+ let response = {};
800
+
801
+ const functionIds = [];
802
+
803
+ if (functionId) {
804
+ functionIds.push(functionId);
805
+ } else if (cliConfig.all) {
806
+ checkDeployConditions(localConfig);
807
+ const functions = localConfig.getFunctions();
808
+ if (functions.length === 0) {
809
+ if (returnOnZero) {
810
+ log('No functions found, skipping');
811
+ return;
812
+ }
813
+ throw new Error("No functions found in the current directory. Use 'appwrite pull functions' to synchronize existing one, or use 'appwrite init function' to create a new one.");
814
+ }
815
+ functionIds.push(...functions.map((func) => {
816
+ return func.$id;
817
+ }));
818
+ }
819
+
820
+ if (functionIds.length <= 0) {
821
+ const answers = await inquirer.prompt(questionsPushFunctions[0]);
822
+ functionIds.push(...answers.functions);
823
+ }
824
+
825
+ let functions = functionIds.map((id) => {
826
+ const functions = localConfig.getFunctions();
827
+ const func = functions.find((f) => f.$id === id);
828
+
829
+ if (!func) {
830
+ throw new Error("Function '" + id + "' not found.")
831
+ }
832
+
833
+ return func;
834
+ });
835
+
836
+ log('Validating functions');
837
+ // Validation is done BEFORE pushing so the deployment process can be run in async with progress update
838
+ for (let func of functions) {
839
+
840
+ if (!func.entrypoint) {
841
+ log(`Function ${func.name} does not have an endpoint`);
842
+ const answers = await inquirer.prompt(questionsGetEntrypoint)
843
+ func.entrypoint = answers.entrypoint;
844
+ localConfig.updateFunction(func['$id'], func);
845
+ }
846
+
847
+ if (func.variables) {
848
+ func.pushVariables = cliConfig.force;
849
+
850
+ try {
851
+ const { total } = await functionsListVariables({
852
+ functionId: func['$id'],
853
+ queries: [JSON.stringify({ method: 'limit', values: [1] })],
854
+ parseOutput: false
855
+ });
856
+
857
+ if (total === 0) {
858
+ func.pushVariables = true;
859
+ } else if (total > 0 && !func.pushVariables) {
860
+ log(`The function ${func.name} has remote variables setup`);
861
+ const variableAnswers = await inquirer.prompt(questionsPushFunctions[1])
862
+ func.pushVariables = variableAnswers.override.toLowerCase() === "yes";
863
+ }
864
+ } catch (e) {
865
+ if (e.code != 404) {
866
+ throw e.message;
867
+ }
868
+ }
869
+ }
870
+ }
871
+
872
+
873
+ log('All functions are validated');
874
+ log('Pushing functions\n');
875
+
876
+ Spinner.start(false);
877
+ let successfullyPushed = 0;
878
+ let successfullyDeployed = 0;
879
+ const failedDeployments = [];
880
+
881
+ await Promise.all(functions.map(async (func) => {
882
+ const ignore = func.ignore ? 'appwrite.json' : '.gitignore';
883
+ let functionExists = false;
884
+ let deploymentCreated = false;
885
+
886
+ const updaterRow = new Spinner({ status: '', resource: func.name, id: func['$id'], end: `Ignoring using: ${ignore}` });
887
+
888
+ updaterRow.update({ status: 'Getting' }).startSpinner(SPINNER_DOTS);
889
+
890
+ try {
891
+ response = await functionsGet({
892
+ functionId: func['$id'],
893
+ parseOutput: false,
894
+ });
895
+ functionExists = true;
896
+ if (response.runtime !== func.runtime) {
897
+ updaterRow.fail({ errorMessage: `Runtime mismatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json` })
898
+ return;
899
+ }
900
+
901
+ updaterRow.update({ status: 'Updating' }).replaceSpinner(SPINNER_ARC);
902
+
903
+ response = await functionsUpdate({
904
+ functionId: func['$id'],
905
+ name: func.name,
906
+ execute: func.execute,
907
+ events: func.events,
908
+ schedule: func.schedule,
909
+ timeout: func.timeout,
910
+ enabled: func.enabled,
911
+ logging: func.logging,
912
+ entrypoint: func.entrypoint,
913
+ commands: func.commands,
914
+ providerRepositoryId: func.providerRepositoryId ?? "",
915
+ installationId: func.installationId ?? '',
916
+ providerBranch: func.providerBranch ?? '',
917
+ providerRootDirectory: func.providerRootDirectory ?? '',
918
+ providerSilentMode: func.providerSilentMode ?? false,
919
+ vars: JSON.stringify(response.vars),
920
+ parseOutput: false
921
+ });
922
+ } catch (e) {
923
+
924
+ if (Number(e.code) === 404) {
925
+ functionExists = false;
926
+ } else {
927
+ updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' });
928
+ return;
929
+ }
930
+ }
931
+
932
+ if (!functionExists) {
933
+ updaterRow.update({ status: 'Creating' }).replaceSpinner(SPINNER_DOTS);
934
+
935
+ try {
936
+ response = await functionsCreate({
937
+ functionId: func.$id || 'unique()',
938
+ name: func.name,
939
+ runtime: func.runtime,
940
+ execute: func.execute,
941
+ events: func.events,
942
+ schedule: func.schedule,
943
+ timeout: func.timeout,
944
+ enabled: func.enabled,
945
+ logging: func.logging,
946
+ entrypoint: func.entrypoint,
947
+ commands: func.commands,
948
+ vars: JSON.stringify(func.vars),
949
+ parseOutput: false
950
+ });
951
+
952
+ localConfig.updateFunction(func['$id'], {
953
+ "$id": response['$id'],
954
+ });
955
+ func["$id"] = response['$id'];
956
+ updaterRow.update({ status: 'Created' });
957
+ } catch (e) {
958
+ updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' });
959
+ return;
960
+ }
961
+ }
962
+
963
+ if (func.variables) {
964
+ if (!func.pushVariables) {
965
+ updaterRow.update({ end: 'Skipping variables' });
966
+ } else {
967
+ updaterRow.update({ end: 'Pushing variables' });
968
+
969
+ const { variables } = await paginate(functionsListVariables, {
970
+ functionId: func['$id'],
971
+ parseOutput: false
972
+ }, 100, 'variables');
973
+
974
+ await Promise.all(variables.map(async variable => {
975
+ await functionsDeleteVariable({
976
+ functionId: func['$id'],
977
+ variableId: variable['$id'],
978
+ parseOutput: false
979
+ });
980
+ }));
981
+
982
+ let result = await awaitPools.wipeVariables(func['$id']);
983
+ if (!result) {
984
+ updaterRow.fail({ errorMessage: 'Variable deletion timed out' })
985
+ return;
986
+ }
987
+
988
+ // Push local variables
989
+ await Promise.all(Object.keys(func.variables).map(async localVariableKey => {
990
+ await functionsCreateVariable({
991
+ functionId: func['$id'],
992
+ key: localVariableKey,
993
+ value: func.variables[localVariableKey],
994
+ parseOutput: false
995
+ });
996
+ }));
997
+ }
998
+ }
999
+
1000
+ try {
1001
+ updaterRow.update({ status: 'Pushing' }).replaceSpinner(SPINNER_ARC);
1002
+ response = await functionsCreateDeployment({
1003
+ functionId: func['$id'],
1004
+ entrypoint: func.entrypoint,
1005
+ commands: func.commands,
1006
+ code: func.path,
1007
+ activate: true,
1008
+ parseOutput: false
1009
+ })
1010
+
1011
+ updaterRow.update({ status: 'Pushed' });
1012
+ deploymentCreated = true;
1013
+ successfullyPushed++;
1014
+ } catch (e) {
1015
+ switch (e.code) {
1016
+ case 'ENOENT':
1017
+ updaterRow.fail({ errorMessage: 'Not found in the current directory. Skipping...' })
1018
+ break;
1019
+ default:
1020
+ updaterRow.fail({ errorMessage: e.message ?? 'An unknown error occurred. Please try again.' })
1021
+ }
1022
+ }
1023
+
1024
+ if (deploymentCreated && !async) {
1025
+ try {
1026
+ const deploymentId = response['$id'];
1027
+ updaterRow.update({ status: 'Deploying', end: 'Checking deployment status...' })
1028
+ let pollChecks = 0;
1029
+
1030
+ while (true) {
1031
+ if (pollChecks >= POLL_MAX_DEBOUNCE) {
1032
+ updaterRow.update({ end: 'Deployment is taking too long. Please check the console for more details.' })
1033
+ break;
1034
+ }
1035
+
1036
+ response = await functionsGetDeployment({
1037
+ functionId: func['$id'],
1038
+ deploymentId: deploymentId,
1039
+ parseOutput: false
1040
+ });
1041
+
1042
+
1043
+ const status = response['status'];
1044
+ if (status === 'ready') {
1045
+ successfullyDeployed++;
1046
+
1047
+ let url = '';
1048
+ const res = await proxyListRules({
1049
+ parseOutput: false,
1050
+ queries: [
1051
+ JSON.stringify({ method: 'limit', values: [1] }),
1052
+ JSON.stringify({ method: 'equal', "attribute": "resourceType", "values": ["function"] }),
1053
+ JSON.stringify({ method: 'equal', "attribute": "resourceId", "values": [func['$id']] })
1054
+ ],
1055
+ });
1056
+
1057
+ if (Number(res.total) === 1) {
1058
+ url = res.rules[0].domain;
1059
+ }
1060
+
1061
+ updaterRow.update({ status: 'Deployed', end: url });
1062
+
1063
+ break;
1064
+ } else if (status === 'failed') {
1065
+ failedDeployments.push({ name: func['name'], $id: func['$id'], deployment: response['$id'] });
1066
+ updaterRow.fail({ errorMessage: `Failed to deploy` });
1067
+
1068
+ break;
1069
+ } else {
1070
+ updaterRow.update({ status: 'Deploying', end: `Current status: ${status}` })
1071
+ }
1072
+
1073
+ pollChecks++;
1074
+ await new Promise(resolve => setTimeout(resolve, POLL_DEBOUNCE));
1075
+ }
1076
+ } catch (e) {
1077
+ updaterRow.fail({ errorMessage: e.message ?? 'Unknown error occurred. Please try again' })
1078
+ }
1079
+ }
1080
+
1081
+ updaterRow.stopSpinner();
1082
+ }));
1083
+
1084
+ Spinner.stop();
1085
+ console.log('\n');
1086
+
1087
+ failedDeployments.forEach((failed) => {
1088
+ const { name, deployment, $id } = failed;
1089
+ const failUrl = `${globalConfig.getEndpoint().replace('/v1', '')}/console/project-${localConfig.getProject().projectId}/functions/function-${$id}/deployment-${deployment}`;
1090
+
1091
+ error(`Deployment of ${name} has failed. Check at ${failUrl} for more details\n`);
1092
+ })
1093
+
1094
+ let message = chalk.green(`Pushed and deployed ${successfullyPushed} functions`);
1095
+
1096
+ if (!async) {
1097
+ if (successfullyDeployed < successfullyPushed) {
1098
+ message = `${chalk.green(`Pushed and deployed ${successfullyPushed} functions.`)} ${chalk.red(`${successfullyPushed - successfullyDeployed} failed to deploy`)}`;
1099
+ } else {
1100
+ if (successfullyPushed === 0) {
1101
+ message = chalk.red(`Error pushing ${functions.length} functions`)
1102
+ }
1103
+ }
1104
+ }
1105
+ log(message);
1106
+ }
1107
+
1108
+ const pushCollection = async ({ returnOnZero } = { returnOnZero: false }) => {
1109
+ const collections = [];
1110
+
1111
+ if (cliConfig.all) {
1112
+ checkDeployConditions(localConfig);
1113
+ if (localConfig.getCollections().length === 0) {
1114
+ if (returnOnZero) {
1115
+ log('No collections found, skipping');
1116
+ return;
1117
+ }
1118
+
1119
+ throw new Error("No collections found in the current directory. Use 'appwrite pull collections' to synchronize existing one, or use 'appwrite init collection' to create a new one.");
1120
+ }
1121
+ collections.push(...localConfig.getCollections());
1122
+ } else {
1123
+ const answers = await inquirer.prompt(questionsPushCollections[0])
1124
+ const configCollections = new Map();
1125
+ localConfig.getCollections().forEach((c) => {
1126
+ configCollections.set(`${c['databaseId']}|${c['$id']}`, c);
1127
+ });
1128
+ answers.collections.forEach((a) => {
1129
+ const collection = configCollections.get(a);
1130
+ collections.push(collection);
1131
+ })
1132
+ }
1133
+ const databases = Array.from(new Set(collections.map(collection => collection['databaseId'])));
1134
+ log('Checking for databases and collection changes');
1135
+
1136
+ // Parallel db actions
1137
+ await Promise.all(databases.map(async (databaseId) => {
1138
+ const localDatabase = localConfig.getDatabase(databaseId);
1139
+
1140
+ try {
1141
+ const database = await databasesGet({
1142
+ databaseId: databaseId,
1143
+ parseOutput: false,
1144
+ });
1145
+
1146
+ if (database.name !== (localDatabase.name ?? databaseId)) {
1147
+ await databasesUpdate({
1148
+ databaseId: databaseId,
1149
+ name: localDatabase.name ?? databaseId,
1150
+ parseOutput: false
1151
+ })
1152
+
1153
+ success(`Updated ${localDatabase.name} ( ${databaseId} ) name`);
1154
+ }
1155
+ } catch (err) {
1156
+ log(`Database ${databaseId} not found. Creating it now...`);
1157
+
1158
+ await databasesCreate({
1159
+ databaseId: databaseId,
1160
+ name: localDatabase.name ?? databaseId,
1161
+ parseOutput: false,
1162
+ });
1163
+ }
1164
+ }));
1165
+
1166
+ // Parallel collection actions
1167
+ await Promise.all(collections.map(async (collection) => {
1168
+ try {
1169
+ const remoteCollection = await databasesGetCollection({
1170
+ databaseId: collection['databaseId'],
1171
+ collectionId: collection['$id'],
1172
+ parseOutput: false,
1173
+ });
1174
+
1175
+ if (remoteCollection.name !== collection.name) {
1176
+ await databasesUpdateCollection({
1177
+ databaseId: collection['databaseId'],
1178
+ collectionId: collection['$id'],
1179
+ name: collection.name,
1180
+ name: collection.name,
1181
+ parseOutput: false
1182
+ })
1183
+
1184
+ success(`Updated ${collection.name} ( ${collection['$id']} ) name`);
1185
+ }
1186
+ collection.remoteVersion = remoteCollection;
1187
+
1188
+ collection.isExisted = true;
1189
+ } catch
1190
+ (e) {
1191
+ if (Number(e.code) === 404) {
1192
+ log(`Collection ${collection.name} does not exist in the project. Creating ... `);
1193
+ await databasesCreateCollection({
1194
+ databaseId: collection['databaseId'],
1195
+ collectionId: collection['$id'],
1196
+ name: collection.name,
1197
+ documentSecurity: collection.documentSecurity,
1198
+ permissions: collection['$permissions'],
1199
+ parseOutput: false
1200
+ })
1201
+ } else {
1202
+ throw e;
1203
+ }
1204
+ }
1205
+ }))
1206
+
1207
+ // Serialize attribute actions
1208
+ for (let collection of collections) {
1209
+ let attributes = collection.attributes;
1210
+
1211
+ if (collection.isExisted) {
1212
+ attributes = await attributesToCreate(collection.remoteVersion.attributes, collection.attributes, collection);
1213
+
1214
+ if (Array.isArray(attributes) && attributes.length <= 0) {
1215
+ continue;
1216
+ }
1217
+ }
1218
+
1219
+ log(`Pushing collection ${collection.name} ( ${collection['databaseId']} - ${collection['$id']} ) attributes`)
1220
+
1221
+ try {
1222
+ await createAttributes(attributes, collection)
1223
+ } catch (e) {
1224
+ throw e;
1225
+ }
1226
+
1227
+ try {
1228
+ await createIndexes(collection.indexes, collection);
1229
+ } catch (e) {
1230
+ throw e;
1231
+ }
1232
+
1233
+ success(`Pushed ${collection.name} ( ${collection['$id']} )`);
1234
+ }
1235
+ }
1236
+
1237
+ const pushBucket = async ({ returnOnZero } = { returnOnZero: false }) => {
1238
+ let response = {};
1239
+
1240
+ let bucketIds = [];
1241
+ const configBuckets = localConfig.getBuckets();
1242
+
1243
+ if (cliConfig.all) {
1244
+ checkDeployConditions(localConfig);
1245
+ if (configBuckets.length === 0) {
1246
+ if (returnOnZero) {
1247
+ log('No buckets found, skipping');
1248
+ return;
1249
+ }
1250
+ throw new Error("No buckets found in the current directory. Use 'appwrite pull buckets' to synchronize existing one, or use 'appwrite init bucket' to create a new one.");
1251
+ }
1252
+ bucketIds.push(...configBuckets.map((b) => b.$id));
1253
+ }
1254
+
1255
+ if (bucketIds.length === 0) {
1256
+ const answers = await inquirer.prompt(questionsPushBuckets[0])
1257
+ bucketIds.push(...answers.buckets);
1258
+ }
1259
+
1260
+ let buckets = [];
1261
+
1262
+ for (const bucketId of bucketIds) {
1263
+ const idBuckets = configBuckets.filter((b) => b.$id === bucketId);
1264
+ buckets.push(...idBuckets);
1265
+ }
1266
+
1267
+ for (let bucket of buckets) {
1268
+ log(`Pushing bucket ${bucket.name} ( ${bucket['$id']} )`)
1269
+
1270
+ try {
1271
+ response = await storageGetBucket({
1272
+ bucketId: bucket['$id'],
1273
+ parseOutput: false,
1274
+ })
1275
+
1276
+ log(`Updating bucket ...`)
1277
+
1278
+ await storageUpdateBucket({
1279
+ bucketId: bucket['$id'],
1280
+ name: bucket.name,
1281
+ permissions: bucket['$permissions'],
1282
+ fileSecurity: bucket.fileSecurity,
1283
+ enabled: bucket.enabled,
1284
+ maximumFileSize: bucket.maximumFileSize,
1285
+ allowedFileExtensions: bucket.allowedFileExtensions,
1286
+ encryption: bucket.encryption,
1287
+ antivirus: bucket.antivirus,
1288
+ compression: bucket.compression,
1289
+ parseOutput: false
1290
+ });
1291
+
1292
+ success(`Pushed ${bucket.name} ( ${bucket['$id']} )`);
1293
+ } catch (e) {
1294
+ if (Number(e.code) === 404) {
1295
+ log(`Bucket ${bucket.name} does not exist in the project. Creating ... `);
1296
+
1297
+ response = await storageCreateBucket({
1298
+ bucketId: bucket['$id'],
1299
+ name: bucket.name,
1300
+ permissions: bucket['$permissions'],
1301
+ fileSecurity: bucket.fileSecurity,
1302
+ enabled: bucket.enabled,
1303
+ maximumFileSize: bucket.maximumFileSize,
1304
+ allowedFileExtensions: bucket.allowedFileExtensions,
1305
+ compression: bucket.compression,
1306
+ encryption: bucket.encryption,
1307
+ antivirus: bucket.antivirus,
1308
+ parseOutput: false
1309
+ })
1310
+
1311
+ success(`Pushed ${bucket.name} ( ${bucket['$id']} )`);
1312
+ } else {
1313
+ throw e;
1314
+ }
1315
+ }
1316
+ }
1317
+ }
1318
+
1319
+ const pushTeam = async ({ returnOnZero } = { returnOnZero: false }) => {
1320
+ let response = {};
1321
+
1322
+ let teamIds = [];
1323
+ const configTeams = localConfig.getTeams();
1324
+
1325
+ if (cliConfig.all) {
1326
+ checkDeployConditions(localConfig);
1327
+ if (configTeams.length === 0) {
1328
+ if (returnOnZero) {
1329
+ log('No teams found, skipping');
1330
+ return;
1331
+ }
1332
+ throw new Error("No teams found in the current directory. Use 'appwrite pull teams' to synchronize existing one, or use 'appwrite init team' to create a new one.");
1333
+ }
1334
+ teamIds.push(...configTeams.map((t) => t.$id));
1335
+ }
1336
+
1337
+ if (teamIds.length === 0) {
1338
+ const answers = await inquirer.prompt(questionsPushTeams[0])
1339
+ teamIds.push(...answers.teams);
1340
+ }
1341
+
1342
+ let teams = [];
1343
+
1344
+ for (const teamId of teamIds) {
1345
+ const idTeams = configTeams.filter((t) => t.$id === teamId);
1346
+ teams.push(...idTeams);
1347
+ }
1348
+
1349
+ for (let team of teams) {
1350
+ log(`Pushing team ${team.name} ( ${team['$id']} )`)
1351
+
1352
+ try {
1353
+ response = await teamsGet({
1354
+ teamId: team['$id'],
1355
+ parseOutput: false,
1356
+ })
1357
+
1358
+ log(`Updating team ...`)
1359
+
1360
+ await teamsUpdateName({
1361
+ teamId: team['$id'],
1362
+ name: team.name,
1363
+ parseOutput: false
1364
+ });
1365
+
1366
+ success(`Pushed ${team.name} ( ${team['$id']} )`);
1367
+ } catch (e) {
1368
+ if (Number(e.code) === 404) {
1369
+ log(`Team ${team.name} does not exist in the project. Creating ... `);
1370
+
1371
+ response = await teamsCreate({
1372
+ teamId: team['$id'],
1373
+ name: team.name,
1374
+ parseOutput: false
1375
+ })
1376
+
1377
+ success(`Pushed ${team.name} ( ${team['$id']} )`);
1378
+ } else {
1379
+ throw e;
1380
+ }
1381
+ }
1382
+ }
1383
+ }
1384
+
1385
+ const pushMessagingTopic = async ({ returnOnZero } = { returnOnZero: false }) => {
1386
+ let response = {};
1387
+
1388
+ let topicsIds = [];
1389
+ const configTopics = localConfig.getMessagingTopics();
1390
+ let overrideExisting = cliConfig.force;
1391
+
1392
+ if (cliConfig.all) {
1393
+ checkDeployConditions(localConfig);
1394
+ if (configTopics.length === 0) {
1395
+ if (returnOnZero) {
1396
+ log('No topics found, skipping');
1397
+ return;
1398
+ }
1399
+ throw new Error("No topics found in the current directory. Use 'appwrite pull topics' to synchronize existing one, or use 'appwrite init topic' to create a new one.");
1400
+ }
1401
+ topicsIds.push(...configTopics.map((b) => b.$id));
1402
+ }
1403
+
1404
+ if (topicsIds.length === 0) {
1405
+ const answers = await inquirer.prompt(questionsPushMessagingTopics[0])
1406
+ topicsIds.push(...answers.topics);
1407
+ }
1408
+
1409
+ let topics = [];
1410
+
1411
+ for (const topicId of topicsIds) {
1412
+ const idTopic = configTopics.filter((b) => b.$id === topicId);
1413
+ topics.push(...idTopic);
1414
+ }
1415
+
1416
+ if (!cliConfig.force) {
1417
+ const answers = await inquirer.prompt(questionsPushMessagingTopics[1])
1418
+ if (answers.override.toLowerCase() === "yes") {
1419
+ overrideExisting = true;
1420
+ }
1421
+ }
1422
+
1423
+ for (let topic of topics) {
1424
+ log(`Pushing topic ${topic.name} ( ${topic['$id']} )`)
1425
+
1426
+ try {
1427
+ response = await messagingGetTopic({
1428
+ topicId: topic['$id'],
1429
+ parseOutput: false
1430
+ })
1431
+ log(`Topic ${topic.name} ( ${topic['$id']} ) already exists.`);
1432
+
1433
+ if (!overrideExisting) {
1434
+ log(`Skipping ${topic.name} ( ${topic['$id']} )`);
1435
+ continue;
1436
+ }
1437
+
1438
+ log(`Updating Topic ...`)
1439
+
1440
+ await messagingUpdateTopic({
1441
+ topicId: topic['$id'],
1442
+ name: topic.name,
1443
+ subscribe: topic.subscribe,
1444
+ parseOutput: false
1445
+ });
1446
+
1447
+ success(`Pushed ${topic.name} ( ${topic['$id']} )`);
1448
+ } catch (e) {
1449
+ if (Number(e.code) === 404) {
1450
+ log(`Topic ${topic.name} does not exist in the project. Creating ... `);
1451
+
1452
+ response = await messagingCreateTopic({
1453
+ topicId: topic['$id'],
1454
+ name: topic.name,
1455
+ subscribe: topic.subscribe,
1456
+ parseOutput: false
1457
+ })
1458
+
1459
+ success(`Created ${topic.name} ( ${topic['$id']} )`);
1460
+ } else {
1461
+ throw e;
1462
+ }
1463
+ }
1464
+ }
1465
+ }
1466
+
1467
+ const push = new Command("push")
1468
+ .description(commandDescriptions['push'])
1469
+ .action(actionRunner(pushResources));
1470
+
1471
+ push
1472
+ .command("all")
1473
+ .description("Push all resource.")
1474
+ .action(actionRunner(() => {
1475
+ cliConfig.all = true;
1476
+ return pushResources();
1477
+ }));
1478
+
1479
+ push
1480
+ .command("project")
1481
+ .description("Push project name, services and auth settings")
1482
+ .action(actionRunner(pushProject));
1483
+
1484
+ push
1485
+ .command("function")
1486
+ .alias("functions")
1487
+ .description("Push functions in the current directory.")
1488
+ .option(`-f, --functionId <functionId>`, `Function ID`)
1489
+ .option(`-A, --async`, `Don't wait for functions deployments status`)
1490
+ .action(actionRunner(pushFunction));
1491
+
1492
+ push
1493
+ .command("collection")
1494
+ .alias("collections")
1495
+ .description("Push collections in the current project.")
1496
+ .action(actionRunner(pushCollection));
1497
+
1498
+ push
1499
+ .command("bucket")
1500
+ .alias("buckets")
1501
+ .description("Push buckets in the current project.")
1502
+ .action(actionRunner(pushBucket));
1503
+
1504
+ push
1505
+ .command("team")
1506
+ .alias("teams")
1507
+ .description("Push teams in the current project.")
1508
+ .action(actionRunner(pushTeam));
1509
+
1510
+ push
1511
+ .command("topic")
1512
+ .alias("topics")
1513
+ .description("Push messaging topics in the current project.")
1514
+ .action(actionRunner(pushMessagingTopic));
1515
+
1516
+ module.exports = {
1517
+ push
1518
+ }