appwrite-utils-cli 0.0.285 → 0.9.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.
Files changed (109) hide show
  1. package/README.md +122 -96
  2. package/dist/collections/attributes.d.ts +4 -0
  3. package/dist/collections/attributes.js +224 -0
  4. package/dist/collections/indexes.d.ts +4 -0
  5. package/dist/collections/indexes.js +27 -0
  6. package/dist/collections/methods.d.ts +16 -0
  7. package/dist/collections/methods.js +216 -0
  8. package/dist/databases/methods.d.ts +6 -0
  9. package/dist/databases/methods.js +33 -0
  10. package/dist/interactiveCLI.d.ts +19 -0
  11. package/dist/interactiveCLI.js +555 -0
  12. package/dist/main.js +227 -62
  13. package/dist/migrations/afterImportActions.js +37 -40
  14. package/dist/migrations/appwriteToX.d.ts +26 -25
  15. package/dist/migrations/appwriteToX.js +42 -6
  16. package/dist/migrations/attributes.js +21 -20
  17. package/dist/migrations/backup.d.ts +93 -87
  18. package/dist/migrations/collections.d.ts +6 -0
  19. package/dist/migrations/collections.js +149 -20
  20. package/dist/migrations/converters.d.ts +2 -18
  21. package/dist/migrations/converters.js +13 -2
  22. package/dist/migrations/dataLoader.d.ts +276 -161
  23. package/dist/migrations/dataLoader.js +535 -292
  24. package/dist/migrations/databases.js +8 -2
  25. package/dist/migrations/helper.d.ts +3 -0
  26. package/dist/migrations/helper.js +21 -0
  27. package/dist/migrations/importController.d.ts +5 -2
  28. package/dist/migrations/importController.js +125 -88
  29. package/dist/migrations/importDataActions.d.ts +9 -1
  30. package/dist/migrations/importDataActions.js +15 -3
  31. package/dist/migrations/indexes.js +3 -2
  32. package/dist/migrations/logging.js +20 -8
  33. package/dist/migrations/migrationHelper.d.ts +9 -4
  34. package/dist/migrations/migrationHelper.js +6 -5
  35. package/dist/migrations/openapi.d.ts +1 -1
  36. package/dist/migrations/openapi.js +33 -18
  37. package/dist/migrations/queue.js +3 -2
  38. package/dist/migrations/relationships.d.ts +2 -2
  39. package/dist/migrations/schemaStrings.js +53 -41
  40. package/dist/migrations/setupDatabase.d.ts +2 -4
  41. package/dist/migrations/setupDatabase.js +24 -105
  42. package/dist/migrations/storage.d.ts +3 -1
  43. package/dist/migrations/storage.js +110 -16
  44. package/dist/migrations/transfer.d.ts +30 -0
  45. package/dist/migrations/transfer.js +337 -0
  46. package/dist/migrations/users.d.ts +2 -1
  47. package/dist/migrations/users.js +78 -43
  48. package/dist/schemas/authUser.d.ts +2 -2
  49. package/dist/storage/methods.d.ts +15 -0
  50. package/dist/storage/methods.js +207 -0
  51. package/dist/storage/schemas.d.ts +687 -0
  52. package/dist/storage/schemas.js +175 -0
  53. package/dist/utils/getClientFromConfig.d.ts +4 -0
  54. package/dist/utils/getClientFromConfig.js +16 -0
  55. package/dist/utils/helperFunctions.d.ts +11 -1
  56. package/dist/utils/helperFunctions.js +38 -0
  57. package/dist/utils/retryFailedPromises.d.ts +2 -0
  58. package/dist/utils/retryFailedPromises.js +21 -0
  59. package/dist/utils/schemaStrings.d.ts +13 -0
  60. package/dist/utils/schemaStrings.js +403 -0
  61. package/dist/utils/setupFiles.js +110 -61
  62. package/dist/utilsController.d.ts +40 -22
  63. package/dist/utilsController.js +164 -84
  64. package/package.json +13 -15
  65. package/src/collections/attributes.ts +483 -0
  66. package/src/collections/indexes.ts +53 -0
  67. package/src/collections/methods.ts +331 -0
  68. package/src/databases/methods.ts +47 -0
  69. package/src/init.ts +64 -64
  70. package/src/interactiveCLI.ts +767 -0
  71. package/src/main.ts +292 -83
  72. package/src/migrations/afterImportActions.ts +553 -490
  73. package/src/migrations/appwriteToX.ts +237 -174
  74. package/src/migrations/attributes.ts +483 -422
  75. package/src/migrations/backup.ts +205 -205
  76. package/src/migrations/collections.ts +545 -300
  77. package/src/migrations/converters.ts +161 -150
  78. package/src/migrations/dataLoader.ts +1615 -1304
  79. package/src/migrations/databases.ts +44 -25
  80. package/src/migrations/dbHelpers.ts +92 -92
  81. package/src/migrations/helper.ts +40 -0
  82. package/src/migrations/importController.ts +448 -384
  83. package/src/migrations/importDataActions.ts +315 -307
  84. package/src/migrations/indexes.ts +40 -37
  85. package/src/migrations/logging.ts +29 -16
  86. package/src/migrations/migrationHelper.ts +207 -201
  87. package/src/migrations/openapi.ts +83 -70
  88. package/src/migrations/queue.ts +118 -119
  89. package/src/migrations/relationships.ts +324 -324
  90. package/src/migrations/schemaStrings.ts +472 -460
  91. package/src/migrations/setupDatabase.ts +118 -219
  92. package/src/migrations/storage.ts +538 -358
  93. package/src/migrations/transfer.ts +608 -0
  94. package/src/migrations/users.ts +362 -285
  95. package/src/migrations/validationRules.ts +63 -63
  96. package/src/schemas/authUser.ts +23 -23
  97. package/src/setup.ts +8 -8
  98. package/src/storage/methods.ts +371 -0
  99. package/src/storage/schemas.ts +205 -0
  100. package/src/types.ts +9 -9
  101. package/src/utils/getClientFromConfig.ts +17 -0
  102. package/src/utils/helperFunctions.ts +181 -127
  103. package/src/utils/index.ts +2 -2
  104. package/src/utils/loadConfigs.ts +59 -59
  105. package/src/utils/retryFailedPromises.ts +27 -0
  106. package/src/utils/schemaStrings.ts +473 -0
  107. package/src/utils/setupFiles.ts +228 -182
  108. package/src/utilsController.ts +325 -194
  109. package/tsconfig.json +37 -37
@@ -0,0 +1,767 @@
1
+ import inquirer from "inquirer";
2
+ import { UtilsController } from "./utilsController.js";
3
+ import { createEmptyCollection, setupDirsFiles } from "./utils/setupFiles.js";
4
+ import { fetchAllDatabases } from "./databases/methods.js";
5
+ import { fetchAllCollections } from "./collections/methods.js";
6
+ import { listBuckets, createBucket } from "./storage/methods.js";
7
+ import {
8
+ Databases,
9
+ Storage,
10
+ Client,
11
+ type Models,
12
+ Compression,
13
+ } from "node-appwrite";
14
+ import { getClient } from "./utils/getClientFromConfig.js";
15
+ import type { TransferOptions } from "./migrations/transfer.js";
16
+ import type { AppwriteConfig, ConfigDatabases } from "appwrite-utils";
17
+ import { ulid } from "ulidx";
18
+
19
+ enum CHOICES {
20
+ CREATE_COLLECTION_CONFIG = "Create collection config file",
21
+ SETUP_DIRS_FILES = "Setup directories and files",
22
+ SETUP_DIRS_FILES_WITH_EXAMPLE_DATA = "Setup directories and files with example data",
23
+ SYNC_DB = "Push local config to Appwrite",
24
+ SYNCHRONIZE_CONFIGURATIONS = "Synchronize configurations",
25
+ TRANSFER_DATA = "Transfer data",
26
+ BACKUP_DATABASE = "Backup database",
27
+ WIPE_DATABASE = "Wipe database",
28
+ GENERATE_SCHEMAS = "Generate schemas",
29
+ IMPORT_DATA = "Import data",
30
+ EXIT = "Exit",
31
+ }
32
+
33
+ export class InteractiveCLI {
34
+ private controller: UtilsController;
35
+
36
+ constructor(currentDir?: string, utilsController?: UtilsController) {
37
+ if (utilsController) {
38
+ this.controller = utilsController;
39
+ } else if (currentDir) {
40
+ this.controller = new UtilsController(currentDir);
41
+ } else {
42
+ throw new Error("Current directory or utils controller is required");
43
+ }
44
+ }
45
+
46
+ async run(): Promise<void> {
47
+ console.log("Welcome to Appwrite Utils CLI Tool by Zach Handley");
48
+ console.log(
49
+ "For more information, visit https://github.com/zachhandley/appwrite-utils"
50
+ );
51
+
52
+ await this.controller.init();
53
+
54
+ while (true) {
55
+ const { action } = await inquirer.prompt([
56
+ {
57
+ type: "list",
58
+ name: "action",
59
+ message: "What would you like to do?",
60
+ choices: Object.values(CHOICES),
61
+ },
62
+ ]);
63
+
64
+ switch (action) {
65
+ case CHOICES.CREATE_COLLECTION_CONFIG:
66
+ await this.createCollectionConfig();
67
+ break;
68
+ case CHOICES.SETUP_DIRS_FILES:
69
+ await setupDirsFiles(false);
70
+ break;
71
+ case CHOICES.SETUP_DIRS_FILES_WITH_EXAMPLE_DATA:
72
+ await setupDirsFiles(true);
73
+ break;
74
+ case CHOICES.SYNCHRONIZE_CONFIGURATIONS:
75
+ await this.synchronizeConfigurations();
76
+ break;
77
+ case CHOICES.SYNC_DB:
78
+ await this.syncDb();
79
+ break;
80
+ case CHOICES.TRANSFER_DATA:
81
+ await this.transferData();
82
+ break;
83
+ case CHOICES.BACKUP_DATABASE:
84
+ await this.backupDatabase();
85
+ break;
86
+ case CHOICES.WIPE_DATABASE:
87
+ await this.wipeDatabase();
88
+ break;
89
+ case CHOICES.GENERATE_SCHEMAS:
90
+ await this.generateSchemas();
91
+ break;
92
+ case CHOICES.IMPORT_DATA:
93
+ await this.importData();
94
+ break;
95
+ case CHOICES.EXIT:
96
+ console.log("Exiting...");
97
+ return;
98
+ }
99
+ }
100
+ }
101
+
102
+ private async selectDatabases(
103
+ databases: Models.Database[],
104
+ message: string,
105
+ multiSelect = true
106
+ ): Promise<Models.Database[]> {
107
+ const choices = databases.map((db) => ({ name: db.name, value: db }));
108
+
109
+ if (multiSelect) {
110
+ choices.unshift({ name: "Select All", value: "ALL" as any });
111
+ choices.push({ name: "Clear Selection", value: "CLEAR" as any });
112
+ }
113
+
114
+ const { selectedDatabases } = await inquirer.prompt([
115
+ {
116
+ type: multiSelect ? "checkbox" : "list",
117
+ name: "selectedDatabases",
118
+ message,
119
+ choices,
120
+ loop: false,
121
+ pageSize: 10,
122
+ },
123
+ ]);
124
+
125
+ if (multiSelect) {
126
+ if (selectedDatabases.includes("ALL")) {
127
+ return databases;
128
+ } else if (selectedDatabases.includes("CLEAR")) {
129
+ return [];
130
+ }
131
+ }
132
+
133
+ return selectedDatabases.filter(
134
+ (db: Models.Database | string): db is Models.Database =>
135
+ typeof db !== "string"
136
+ );
137
+ }
138
+
139
+ private async selectCollections(
140
+ database: Models.Database,
141
+ databasesClient: Databases,
142
+ message: string,
143
+ multiSelect = true
144
+ ): Promise<Models.Collection[]> {
145
+ const collections = await fetchAllCollections(
146
+ database.$id,
147
+ databasesClient
148
+ );
149
+ const choices = collections.map((collection) => ({
150
+ name: collection.name,
151
+ value: collection,
152
+ }));
153
+
154
+ if (multiSelect) {
155
+ choices.unshift({ name: "Select All", value: "ALL" as any });
156
+ choices.push({ name: "Clear Selection", value: "CLEAR" as any });
157
+ }
158
+
159
+ const { selectedCollections } = await inquirer.prompt([
160
+ {
161
+ type: multiSelect ? "checkbox" : "list",
162
+ name: "selectedCollections",
163
+ message,
164
+ choices,
165
+ loop: false,
166
+ pageSize: 10,
167
+ },
168
+ ]);
169
+
170
+ if (multiSelect) {
171
+ if (selectedCollections.includes("ALL")) {
172
+ return collections;
173
+ } else if (selectedCollections.includes("CLEAR")) {
174
+ return [];
175
+ }
176
+ }
177
+
178
+ return selectedCollections.filter(
179
+ (
180
+ collection: Models.Collection | string
181
+ ): collection is Models.Collection => typeof collection !== "string"
182
+ );
183
+ }
184
+
185
+ private async selectBuckets(
186
+ buckets: Models.Bucket[],
187
+ message: string,
188
+ multiSelect = true
189
+ ): Promise<Models.Bucket[]> {
190
+ const choices = buckets.map((bucket) => ({
191
+ name: bucket.name,
192
+ value: bucket.$id,
193
+ }));
194
+
195
+ if (multiSelect) {
196
+ choices.unshift({ name: "Select All", value: "ALL" });
197
+ choices.push({ name: "Clear Selection", value: "CLEAR" });
198
+ }
199
+
200
+ const { selectedBuckets } = await inquirer.prompt([
201
+ {
202
+ type: multiSelect ? "checkbox" : "list",
203
+ name: "selectedBuckets",
204
+ message,
205
+ choices,
206
+ loop: false,
207
+ pageSize: 10,
208
+ },
209
+ ]);
210
+
211
+ if (multiSelect) {
212
+ if (selectedBuckets.includes("ALL")) {
213
+ return buckets;
214
+ } else if (selectedBuckets.includes("CLEAR")) {
215
+ return [];
216
+ }
217
+ }
218
+
219
+ return selectedBuckets.map(
220
+ (id: string) => buckets.find((bucket) => bucket.$id === id)!
221
+ );
222
+ }
223
+
224
+ private async createCollectionConfig(): Promise<void> {
225
+ const { collectionName } = await inquirer.prompt([
226
+ {
227
+ type: "input",
228
+ name: "collectionName",
229
+ message: "Enter the name of the collection:",
230
+ validate: (input) =>
231
+ input.trim() !== "" || "Collection name cannot be empty.",
232
+ },
233
+ ]);
234
+ console.log(`Creating collection config file for '${collectionName}'...`);
235
+ createEmptyCollection(collectionName);
236
+ }
237
+
238
+ private async configureBuckets(
239
+ config: AppwriteConfig,
240
+ databases?: ConfigDatabases
241
+ ): Promise<AppwriteConfig> {
242
+ const { storage } = this.controller;
243
+ if (!storage) {
244
+ throw new Error(
245
+ "Storage is not initialized. Is the config file correct and created?"
246
+ );
247
+ }
248
+
249
+ const allBuckets = await listBuckets(storage);
250
+
251
+ // If there are no buckets, ask to create one for each database
252
+ if (allBuckets.total === 0) {
253
+ for (const database of databases ?? config.databases) {
254
+ const { wantCreateBucket } = await inquirer.prompt([
255
+ {
256
+ type: "confirm",
257
+ name: "wantCreateBucket",
258
+ message: `There are no buckets. Do you want to create a bucket for the database "${database.name}"?`,
259
+ default: true,
260
+ },
261
+ ]);
262
+ if (wantCreateBucket) {
263
+ const createdBucket = await this.createNewBucket(
264
+ storage,
265
+ database.name
266
+ );
267
+ database.bucket = {
268
+ ...createdBucket,
269
+ compression: createdBucket.compression as Compression,
270
+ };
271
+ }
272
+ }
273
+ return config;
274
+ }
275
+
276
+ // Configure global buckets
277
+ let globalBuckets: Models.Bucket[] = [];
278
+ if (allBuckets.total > 0) {
279
+ globalBuckets = await this.selectBuckets(
280
+ allBuckets.buckets,
281
+ "Select global buckets (buckets that are not associated with any specific database):",
282
+ true
283
+ );
284
+
285
+ config.buckets = globalBuckets.map((bucket) => ({
286
+ $id: bucket.$id,
287
+ name: bucket.name,
288
+ enabled: bucket.enabled,
289
+ maximumFileSize: bucket.maximumFileSize,
290
+ allowedFileExtensions: bucket.allowedFileExtensions,
291
+ compression: bucket.compression as Compression,
292
+ encryption: bucket.encryption,
293
+ antivirus: bucket.antivirus,
294
+ }));
295
+ } else {
296
+ config.buckets = [];
297
+ }
298
+
299
+ // Configure database-specific buckets
300
+ for (const database of config.databases) {
301
+ const { assignBucket } = await inquirer.prompt([
302
+ {
303
+ type: "confirm",
304
+ name: "assignBucket",
305
+ message: `Do you want to assign or create a bucket for the database "${database.name}"?`,
306
+ default: false,
307
+ },
308
+ ]);
309
+
310
+ if (assignBucket) {
311
+ const { action } = await inquirer.prompt([
312
+ {
313
+ type: "list",
314
+ name: "action",
315
+ message: `Choose an action for the database "${database.name}":`,
316
+ choices: [
317
+ { name: "Assign existing bucket", value: "assign" },
318
+ { name: "Create new bucket", value: "create" },
319
+ ],
320
+ },
321
+ ]);
322
+
323
+ if (action === "assign") {
324
+ const [selectedBucket] = await this.selectBuckets(
325
+ allBuckets.buckets.filter(
326
+ (b) => !globalBuckets.some((gb) => gb.$id === b.$id)
327
+ ),
328
+ `Select a bucket for the database "${database.name}":`,
329
+ false
330
+ );
331
+
332
+ if (selectedBucket) {
333
+ database.bucket = {
334
+ $id: selectedBucket.$id,
335
+ name: selectedBucket.name,
336
+ enabled: selectedBucket.enabled,
337
+ maximumFileSize: selectedBucket.maximumFileSize,
338
+ allowedFileExtensions: selectedBucket.allowedFileExtensions,
339
+ compression: selectedBucket.compression as Compression,
340
+ encryption: selectedBucket.encryption,
341
+ antivirus: selectedBucket.antivirus,
342
+ };
343
+ }
344
+ } else if (action === "create") {
345
+ const createdBucket = await this.createNewBucket(
346
+ storage,
347
+ database.name
348
+ );
349
+ database.bucket = {
350
+ ...createdBucket,
351
+ compression: createdBucket.compression as Compression,
352
+ };
353
+ }
354
+ }
355
+ }
356
+
357
+ return config;
358
+ }
359
+
360
+ private async createNewBucket(
361
+ storage: Storage,
362
+ databaseName: string
363
+ ): Promise<Models.Bucket> {
364
+ const {
365
+ bucketName,
366
+ bucketEnabled,
367
+ bucketMaximumFileSize,
368
+ bucketAllowedFileExtensions,
369
+ bucketFileSecurity,
370
+ bucketCompression,
371
+ bucketCompressionType,
372
+ bucketEncryption,
373
+ bucketAntivirus,
374
+ bucketId,
375
+ } = await inquirer.prompt([
376
+ {
377
+ type: "input",
378
+ name: "bucketName",
379
+ message: `Enter the name of the bucket for database "${databaseName}":`,
380
+ default: `${databaseName}-bucket`,
381
+ },
382
+ {
383
+ type: "confirm",
384
+ name: "bucketEnabled",
385
+ message: "Is the bucket enabled?",
386
+ default: true,
387
+ },
388
+ {
389
+ type: "confirm",
390
+ name: "bucketFileSecurity",
391
+ message: "Do you want to enable file security for the bucket?",
392
+ default: false,
393
+ },
394
+ {
395
+ type: "number",
396
+ name: "bucketMaximumFileSize",
397
+ message: "Enter the maximum file size for the bucket (MB):",
398
+ default: 1000000,
399
+ },
400
+ {
401
+ type: "input",
402
+ name: "bucketAllowedFileExtensions",
403
+ message:
404
+ "Enter the allowed file extensions for the bucket (comma separated):",
405
+ default: "",
406
+ },
407
+ {
408
+ type: "confirm",
409
+ name: "bucketCompression",
410
+ message: "Do you want to enable compression for the bucket?",
411
+ default: false,
412
+ },
413
+ {
414
+ type: "list",
415
+ name: "bucketCompressionType",
416
+ message: "Select the compression type for the bucket:",
417
+ choices: Object.values(Compression),
418
+ default: Compression.None,
419
+ when: (answers) => answers.bucketCompression,
420
+ },
421
+ {
422
+ type: "confirm",
423
+ name: "bucketEncryption",
424
+ message: "Do you want to enable encryption for the bucket?",
425
+ default: false,
426
+ },
427
+ {
428
+ type: "confirm",
429
+ name: "bucketAntivirus",
430
+ message: "Do you want to enable antivirus for the bucket?",
431
+ default: false,
432
+ },
433
+ {
434
+ type: "input",
435
+ name: "bucketId",
436
+ message: "Enter the ID of the bucket (or empty for auto-generation):",
437
+ },
438
+ ]);
439
+
440
+ return await createBucket(
441
+ storage,
442
+ {
443
+ name: bucketName,
444
+ $permissions: [],
445
+ enabled: bucketEnabled,
446
+ fileSecurity: bucketFileSecurity,
447
+ maximumFileSize: bucketMaximumFileSize * 1024 * 1024,
448
+ allowedFileExtensions:
449
+ bucketAllowedFileExtensions.length > 0
450
+ ? bucketAllowedFileExtensions?.split(",")
451
+ : [],
452
+ compression: bucketCompressionType as Compression,
453
+ encryption: bucketEncryption,
454
+ antivirus: bucketAntivirus,
455
+ },
456
+ bucketId.length > 0 ? bucketId : ulid()
457
+ );
458
+ }
459
+
460
+ private async syncDb(): Promise<void> {
461
+ await this.controller.syncDb();
462
+ }
463
+
464
+ private async synchronizeConfigurations(): Promise<void> {
465
+ if (!this.controller.database) {
466
+ throw new Error(
467
+ "Database is not initialized. Is the config file correct and created?"
468
+ );
469
+ }
470
+ const databases = await fetchAllDatabases(this.controller.database);
471
+
472
+ const selectedDatabases = await this.selectDatabases(
473
+ databases,
474
+ "Select databases to synchronize (or select none to synchronize all):"
475
+ );
476
+
477
+ console.log("Configuring storage buckets...");
478
+ const updatedConfig = await this.configureBuckets(
479
+ this.controller.config!,
480
+ selectedDatabases
481
+ );
482
+
483
+ console.log("Synchronizing configurations...");
484
+ await this.controller.synchronizeConfigurations(
485
+ selectedDatabases,
486
+ updatedConfig
487
+ );
488
+ }
489
+
490
+ private async backupDatabase(): Promise<void> {
491
+ if (!this.controller.database) {
492
+ throw new Error(
493
+ "Database is not initialized, is the config file correct & created?"
494
+ );
495
+ }
496
+ const databases = await fetchAllDatabases(this.controller.database);
497
+
498
+ const selectedDatabases = await this.selectDatabases(
499
+ databases,
500
+ "Select databases to backup (or select none to backup all):"
501
+ );
502
+
503
+ for (const db of selectedDatabases) {
504
+ console.log(`Backing up database: ${db.name}`);
505
+ await this.controller.backupDatabase(db);
506
+ }
507
+ }
508
+
509
+ private async wipeDatabase(): Promise<void> {
510
+ if (!this.controller.database || !this.controller.storage) {
511
+ throw new Error(
512
+ "Database or Storage is not initialized, is the config file correct & created?"
513
+ );
514
+ }
515
+ const databases = await fetchAllDatabases(this.controller.database);
516
+ const storage = await listBuckets(this.controller.storage);
517
+
518
+ const selectedDatabases = await this.selectDatabases(
519
+ databases,
520
+ "Select databases to wipe (or select none to skip database wipe):"
521
+ );
522
+
523
+ const { selectedStorage } = await inquirer.prompt([
524
+ {
525
+ type: "checkbox",
526
+ name: "selectedStorage",
527
+ message:
528
+ "Select storage buckets to wipe (or select none to skip storage wipe):",
529
+ choices: storage.buckets.map((s) => ({ name: s.name, value: s.$id })),
530
+ },
531
+ ]);
532
+
533
+ const { wipeUsers } = await inquirer.prompt([
534
+ {
535
+ type: "confirm",
536
+ name: "wipeUsers",
537
+ message: "Do you want to wipe users as well?",
538
+ default: false,
539
+ },
540
+ ]);
541
+
542
+ const { confirm } = await inquirer.prompt([
543
+ {
544
+ type: "confirm",
545
+ name: "confirm",
546
+ message:
547
+ "Are you sure you want to wipe the selected items? This action cannot be undone.",
548
+ default: false,
549
+ },
550
+ ]);
551
+
552
+ if (confirm) {
553
+ console.log("Wiping selected items...");
554
+ for (const db of selectedDatabases) {
555
+ await this.controller.wipeDatabase(db);
556
+ }
557
+ for (const bucketId of selectedStorage) {
558
+ await this.controller.wipeDocumentStorage(bucketId);
559
+ }
560
+ if (wipeUsers) {
561
+ await this.controller.wipeUsers();
562
+ }
563
+ } else {
564
+ console.log("Wipe operation cancelled.");
565
+ }
566
+ }
567
+
568
+ private async generateSchemas(): Promise<void> {
569
+ console.log("Generating schemas...");
570
+ await this.controller.generateSchemas();
571
+ }
572
+
573
+ private async importData(): Promise<void> {
574
+ if (!this.controller.database) {
575
+ throw new Error(
576
+ "Database is not initialized, is the config file correct & created?"
577
+ );
578
+ }
579
+ const databases = await fetchAllDatabases(this.controller.database);
580
+
581
+ const selectedDatabases = await this.selectDatabases(
582
+ databases,
583
+ "Select the database(s) to import data into:"
584
+ );
585
+
586
+ let selectedCollections: Models.Collection[] = [];
587
+ for (const db of selectedDatabases) {
588
+ const dbCollections = await this.selectCollections(
589
+ db,
590
+ this.controller.database,
591
+ `Select collections to import data into for database ${db.name} (or select none to import into all):`,
592
+ true
593
+ );
594
+ selectedCollections = [...selectedCollections, ...dbCollections];
595
+ }
596
+
597
+ const { shouldWriteFile } = await inquirer.prompt([
598
+ {
599
+ type: "confirm",
600
+ name: "shouldWriteFile",
601
+ message: "Do you want to write the imported data to a file?",
602
+ default: false,
603
+ },
604
+ ]);
605
+
606
+ const { checkDuplicates } = await inquirer.prompt([
607
+ {
608
+ type: "confirm",
609
+ name: "checkDuplicates",
610
+ message: "Do you want to check for duplicates during import?",
611
+ default: true,
612
+ },
613
+ ]);
614
+
615
+ console.log("Importing data...");
616
+ await this.controller.importData({
617
+ databases: selectedDatabases,
618
+ collections:
619
+ selectedCollections.length > 0
620
+ ? selectedCollections.map((c) => c.$id)
621
+ : undefined,
622
+ shouldWriteFile,
623
+ checkDuplicates,
624
+ });
625
+ }
626
+
627
+ private async transferData(): Promise<void> {
628
+ if (!this.controller.database) {
629
+ throw new Error(
630
+ "Database is not initialized, is the config file correct & created?"
631
+ );
632
+ }
633
+
634
+ const { isRemote } = await inquirer.prompt([
635
+ {
636
+ type: "confirm",
637
+ name: "isRemote",
638
+ message: "Is this a remote transfer?",
639
+ default: false,
640
+ },
641
+ ]);
642
+
643
+ let sourceClient = this.controller.database;
644
+ let targetClient: Databases;
645
+ let sourceDatabases: Models.Database[];
646
+ let targetDatabases: Models.Database[];
647
+ let remoteOptions:
648
+ | {
649
+ transferEndpoint: string;
650
+ transferProject: string;
651
+ transferKey: string;
652
+ }
653
+ | undefined;
654
+
655
+ if (isRemote) {
656
+ remoteOptions = await inquirer.prompt([
657
+ {
658
+ type: "input",
659
+ name: "transferEndpoint",
660
+ message: "Enter the remote endpoint:",
661
+ },
662
+ {
663
+ type: "input",
664
+ name: "transferProject",
665
+ message: "Enter the remote project ID:",
666
+ },
667
+ {
668
+ type: "input",
669
+ name: "transferKey",
670
+ message: "Enter the remote API key:",
671
+ },
672
+ ]);
673
+
674
+ const remoteClient = getClient(
675
+ remoteOptions!.transferEndpoint,
676
+ remoteOptions!.transferProject,
677
+ remoteOptions!.transferKey
678
+ );
679
+ targetClient = new Databases(remoteClient);
680
+
681
+ sourceDatabases = await fetchAllDatabases(sourceClient);
682
+ targetDatabases = await fetchAllDatabases(targetClient);
683
+ } else {
684
+ targetClient = sourceClient;
685
+ sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
686
+ }
687
+
688
+ const [fromDb] = await this.selectDatabases(
689
+ sourceDatabases,
690
+ "Select the source database:",
691
+ false
692
+ );
693
+ const [targetDb] = await this.selectDatabases(
694
+ targetDatabases.filter((db) => db.$id !== fromDb.$id),
695
+ "Select the target database:",
696
+ false
697
+ );
698
+
699
+ const selectedCollections = await this.selectCollections(
700
+ fromDb,
701
+ sourceClient,
702
+ "Select collections to transfer (or select none to transfer all):"
703
+ );
704
+
705
+ const { transferStorage } = await inquirer.prompt([
706
+ {
707
+ type: "confirm",
708
+ name: "transferStorage",
709
+ message: "Do you want to transfer storage as well?",
710
+ default: false,
711
+ },
712
+ ]);
713
+
714
+ let sourceBucket, targetBucket;
715
+
716
+ if (transferStorage) {
717
+ const sourceStorage = new Storage(this.controller.appwriteServer!);
718
+ const targetStorage = isRemote
719
+ ? new Storage(
720
+ getClient(
721
+ remoteOptions!.transferEndpoint,
722
+ remoteOptions!.transferProject,
723
+ remoteOptions!.transferKey
724
+ )
725
+ )
726
+ : sourceStorage;
727
+
728
+ const sourceBuckets = await listBuckets(sourceStorage);
729
+ const targetBuckets = isRemote
730
+ ? await listBuckets(targetStorage)
731
+ : sourceBuckets;
732
+
733
+ [sourceBucket] = await this.selectBuckets(
734
+ sourceBuckets.buckets,
735
+ "Select the source bucket:",
736
+ false
737
+ );
738
+ [targetBucket] = await this.selectBuckets(
739
+ targetBuckets.buckets,
740
+ "Select the target bucket:",
741
+ false
742
+ );
743
+ }
744
+
745
+ let transferOptions: TransferOptions = {
746
+ fromDb,
747
+ targetDb,
748
+ isRemote,
749
+ collections:
750
+ selectedCollections.length > 0
751
+ ? selectedCollections.map((c) => c.$id)
752
+ : undefined,
753
+ sourceBucket,
754
+ targetBucket,
755
+ };
756
+
757
+ if (isRemote && remoteOptions) {
758
+ transferOptions = {
759
+ ...transferOptions,
760
+ ...remoteOptions,
761
+ };
762
+ }
763
+
764
+ console.log("Transferring data...");
765
+ await this.controller.transferData(transferOptions);
766
+ }
767
+ }