appwrite-utils-cli 0.9.77 → 0.9.78

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.
@@ -15,6 +15,7 @@ import { getClient } from "./utils/getClientFromConfig.js";
15
15
  import type { TransferOptions } from "./migrations/transfer.js";
16
16
  import type { AppwriteConfig, ConfigDatabases } from "appwrite-utils";
17
17
  import { ulid } from "ulidx";
18
+ import chalk from "chalk";
18
19
 
19
20
  enum CHOICES {
20
21
  CREATE_COLLECTION_CONFIG = "Create collection config file",
@@ -25,8 +26,10 @@ enum CHOICES {
25
26
  TRANSFER_DATA = "Transfer data",
26
27
  BACKUP_DATABASE = "Backup database",
27
28
  WIPE_DATABASE = "Wipe database",
29
+ WIPE_COLLECTIONS = "Wipe collections",
28
30
  GENERATE_SCHEMAS = "Generate schemas",
29
31
  IMPORT_DATA = "Import data",
32
+ RELOAD_CONFIG = "Reload configuration files",
30
33
  EXIT = "Exit",
31
34
  }
32
35
 
@@ -36,9 +39,9 @@ export class InteractiveCLI {
36
39
  constructor(private currentDir: string) {}
37
40
 
38
41
  async run(): Promise<void> {
39
- console.log("Welcome to Appwrite Utils CLI Tool by Zach Handley");
42
+ console.log(chalk.green("Welcome to Appwrite Utils CLI Tool by Zach Handley"));
40
43
  console.log(
41
- "For more information, visit https://github.com/zachhandley/AppwriteUtils"
44
+ chalk.blue("For more information, visit https://github.com/zachhandley/AppwriteUtils")
42
45
  );
43
46
 
44
47
  while (true) {
@@ -46,11 +49,13 @@ export class InteractiveCLI {
46
49
  {
47
50
  type: "list",
48
51
  name: "action",
49
- message: "What would you like to do?",
52
+ message: chalk.yellow("What would you like to do?"),
50
53
  choices: Object.values(CHOICES),
51
54
  },
52
55
  ]);
53
56
 
57
+ await this.initControllerIfNeeded();
58
+
54
59
  switch (action) {
55
60
  case CHOICES.CREATE_COLLECTION_CONFIG:
56
61
  await this.createCollectionConfig();
@@ -81,6 +86,10 @@ export class InteractiveCLI {
81
86
  await this.initControllerIfNeeded();
82
87
  await this.wipeDatabase();
83
88
  break;
89
+ case CHOICES.WIPE_COLLECTIONS:
90
+ await this.initControllerIfNeeded();
91
+ await this.wipeCollections();
92
+ break;
84
93
  case CHOICES.GENERATE_SCHEMAS:
85
94
  await this.initControllerIfNeeded();
86
95
  await this.generateSchemas();
@@ -89,14 +98,18 @@ export class InteractiveCLI {
89
98
  await this.initControllerIfNeeded();
90
99
  await this.importData();
91
100
  break;
101
+ case CHOICES.RELOAD_CONFIG:
102
+ await this.initControllerIfNeeded();
103
+ await this.reloadConfig();
104
+ break;
92
105
  case CHOICES.EXIT:
93
- console.log("Exiting...");
106
+ console.log(chalk.green("Goodbye!"));
94
107
  return;
95
108
  }
96
109
  }
97
110
  }
98
111
 
99
- private async initControllerIfNeeded() {
112
+ private async initControllerIfNeeded(): Promise<void> {
100
113
  if (!this.controller) {
101
114
  this.controller = new UtilsController(this.currentDir);
102
115
  await this.controller.init();
@@ -108,13 +121,13 @@ export class InteractiveCLI {
108
121
  message: string,
109
122
  multiSelect = true
110
123
  ): Promise<Models.Database[]> {
111
- const choices = databases.map((db) => ({ name: db.name, value: db }));
124
+ const choices = databases.map((db) => ({ name: db.name, value: db })).filter((db) => db.name.toLowerCase() !== "migrations");
112
125
 
113
126
  const { selectedDatabases } = await inquirer.prompt([
114
127
  {
115
128
  type: multiSelect ? "checkbox" : "list",
116
129
  name: "selectedDatabases",
117
- message,
130
+ message: chalk.blue(message),
118
131
  choices,
119
132
  loop: false,
120
133
  pageSize: 10,
@@ -143,7 +156,7 @@ export class InteractiveCLI {
143
156
  {
144
157
  type: multiSelect ? "checkbox" : "list",
145
158
  name: "selectedCollections",
146
- message,
159
+ message: chalk.blue(message),
147
160
  choices,
148
161
  loop: false,
149
162
  pageSize: 10,
@@ -167,7 +180,7 @@ export class InteractiveCLI {
167
180
  {
168
181
  type: multiSelect ? "checkbox" : "list",
169
182
  name: "selectedBuckets",
170
- message,
183
+ message: chalk.blue(message),
171
184
  choices,
172
185
  loop: false,
173
186
  pageSize: 10,
@@ -182,12 +195,12 @@ export class InteractiveCLI {
182
195
  {
183
196
  type: "input",
184
197
  name: "collectionName",
185
- message: "Enter the name of the collection:",
198
+ message: chalk.blue("Enter the name of the collection:"),
186
199
  validate: (input) =>
187
200
  input.trim() !== "" || "Collection name cannot be empty.",
188
201
  },
189
202
  ]);
190
- console.log(`Creating collection config file for '${collectionName}'...`);
203
+ console.log(chalk.green(`Creating collection config file for '${collectionName}'...`));
191
204
  createEmptyCollection(collectionName);
192
205
  }
193
206
 
@@ -211,7 +224,7 @@ export class InteractiveCLI {
211
224
  {
212
225
  type: "confirm",
213
226
  name: "wantCreateBucket",
214
- message: `There are no buckets. Do you want to create a bucket for the database "${database.name}"?`,
227
+ message: chalk.blue(`There are no buckets. Do you want to create a bucket for the database "${database.name}"?`),
215
228
  default: true,
216
229
  },
217
230
  ]);
@@ -414,7 +427,20 @@ export class InteractiveCLI {
414
427
  }
415
428
 
416
429
  private async syncDb(): Promise<void> {
417
- await this.controller!.syncDb();
430
+ console.log(chalk.yellow("Syncing database..."));
431
+ const databases = await this.selectDatabases(
432
+ await fetchAllDatabases(this.controller!.database!),
433
+ chalk.blue("Select databases to synchronize:"),
434
+ true,
435
+ );
436
+ const collections = await this.selectCollections(
437
+ databases[0],
438
+ this.controller!.database!,
439
+ chalk.blue("Select collections to synchronize:"),
440
+ true,
441
+ );
442
+ await this.controller!.syncDb(databases, collections);
443
+ console.log(chalk.green("Database sync completed."));
418
444
  }
419
445
 
420
446
  private async synchronizeConfigurations(): Promise<void> {
@@ -430,17 +456,18 @@ export class InteractiveCLI {
430
456
  "Select databases to synchronize:"
431
457
  );
432
458
 
433
- console.log("Configuring storage buckets...");
459
+ console.log(chalk.yellow("Configuring storage buckets..."));
434
460
  const updatedConfig = await this.configureBuckets(
435
461
  this.controller!.config!,
436
462
  selectedDatabases
437
463
  );
438
464
 
439
- console.log("Synchronizing configurations...");
465
+ console.log(chalk.yellow("Synchronizing configurations..."));
440
466
  await this.controller!.synchronizeConfigurations(
441
467
  selectedDatabases,
442
468
  updatedConfig
443
469
  );
470
+ console.log(chalk.green("Configuration synchronization completed."));
444
471
  }
445
472
 
446
473
  private async backupDatabase(): Promise<void> {
@@ -457,9 +484,10 @@ export class InteractiveCLI {
457
484
  );
458
485
 
459
486
  for (const db of selectedDatabases) {
460
- console.log(`Backing up database: ${db.name}`);
487
+ console.log(chalk.yellow(`Backing up database: ${db.name}`));
461
488
  await this.controller!.backupDatabase(db);
462
489
  }
490
+ console.log(chalk.green("Database backup completed."));
463
491
  }
464
492
 
465
493
  private async wipeDatabase(): Promise<void> {
@@ -498,14 +526,15 @@ export class InteractiveCLI {
498
526
  {
499
527
  type: "confirm",
500
528
  name: "confirm",
501
- message:
502
- "Are you sure you want to wipe the selected items? This action cannot be undone.",
529
+ message: chalk.red(
530
+ "Are you sure you want to wipe the selected items? This action cannot be undone."
531
+ ),
503
532
  default: false,
504
533
  },
505
534
  ]);
506
535
 
507
536
  if (confirm) {
508
- console.log("Wiping selected items...");
537
+ console.log(chalk.yellow("Wiping selected items..."));
509
538
  for (const db of selectedDatabases) {
510
539
  await this.controller!.wipeDatabase(db);
511
540
  }
@@ -515,39 +544,87 @@ export class InteractiveCLI {
515
544
  if (wipeUsers) {
516
545
  await this.controller!.wipeUsers();
517
546
  }
547
+ console.log(chalk.green("Wipe operation completed."));
518
548
  } else {
519
- console.log("Wipe operation cancelled.");
549
+ console.log(chalk.blue("Wipe operation cancelled."));
520
550
  }
521
551
  }
522
552
 
523
- private async generateSchemas(): Promise<void> {
524
- console.log("Generating schemas...");
525
- await this.controller!.generateSchemas();
526
- }
527
-
528
- private async importData(): Promise<void> {
553
+ private async wipeCollections(): Promise<void> {
529
554
  if (!this.controller!.database) {
530
555
  throw new Error(
531
556
  "Database is not initialized, is the config file correct & created?"
532
557
  );
533
558
  }
534
559
  const databases = await fetchAllDatabases(this.controller!.database);
535
-
536
560
  const selectedDatabases = await this.selectDatabases(
537
561
  databases,
538
- "Select the database(s) to import data into:"
562
+ "Select the database(s) containing the collections to wipe:",
563
+ true
539
564
  );
540
565
 
541
- let selectedCollections: Models.Collection[] = [];
542
- for (const db of selectedDatabases) {
543
- const dbCollections = await this.selectCollections(
544
- db,
566
+ for (const database of selectedDatabases) {
567
+ const collections = await this.selectCollections(
568
+ database,
545
569
  this.controller!.database,
546
- `Select collections to import data into for database ${db.name}:`,
570
+ `Select collections to wipe from ${database.name}:`,
547
571
  true
548
572
  );
549
- selectedCollections = [...selectedCollections, ...dbCollections];
573
+
574
+ const { confirm } = await inquirer.prompt([
575
+ {
576
+ type: "confirm",
577
+ name: "confirm",
578
+ message: chalk.red(
579
+ `Are you sure you want to wipe the selected collections from ${database.name}? This action cannot be undone.`
580
+ ),
581
+ default: false,
582
+ },
583
+ ]);
584
+
585
+ if (confirm) {
586
+ console.log(chalk.yellow(`Wiping selected collections from ${database.name}...`));
587
+ for (const collection of collections) {
588
+ await this.controller!.wipeCollection(database, collection);
589
+ console.log(chalk.green(`Collection ${collection.name} wiped successfully.`));
590
+ }
591
+ } else {
592
+ console.log(chalk.blue(`Wipe operation cancelled for ${database.name}.`));
593
+ }
550
594
  }
595
+ console.log(chalk.green("Wipe collections operation completed."));
596
+ }
597
+
598
+ private async generateSchemas(): Promise<void> {
599
+ console.log(chalk.yellow("Generating schemas..."));
600
+ await this.controller!.generateSchemas();
601
+ console.log(chalk.green("Schema generation completed."));
602
+ }
603
+
604
+ private async importData(): Promise<void> {
605
+ console.log(chalk.yellow("Importing data..."));
606
+
607
+ const { doBackup } = await inquirer.prompt([
608
+ {
609
+ type: "confirm",
610
+ name: "doBackup",
611
+ message: "Do you want to perform a backup before importing?",
612
+ default: true,
613
+ },
614
+ ]);
615
+
616
+ const databases = await this.selectDatabases(
617
+ await fetchAllDatabases(this.controller!.database!),
618
+ "Select databases to import data into:",
619
+ true
620
+ );
621
+
622
+ const collections = await this.selectCollections(
623
+ databases[0],
624
+ this.controller!.database!,
625
+ "Select collections to import data into (leave empty for all):",
626
+ true
627
+ );
551
628
 
552
629
  const { shouldWriteFile } = await inquirer.prompt([
553
630
  {
@@ -558,26 +635,20 @@ export class InteractiveCLI {
558
635
  },
559
636
  ]);
560
637
 
561
- // const { checkDuplicates } = await inquirer.prompt([
562
- // {
563
- // type: "confirm",
564
- // name: "checkDuplicates",
565
- // message: "Do you want to check for duplicates during import?",
566
- // default: true,
567
- // },
568
- // ]);
569
-
570
- console.log("Importing data...");
571
- await this.controller!.importData({
572
- databases: selectedDatabases,
573
- collections:
574
- selectedCollections.length > 0
575
- ? selectedCollections.map((c) => c.$id)
576
- : undefined,
638
+ const options = {
639
+ databases,
640
+ collections: collections.map(c => c.name),
641
+ doBackup,
642
+ importData: true,
577
643
  shouldWriteFile,
578
- checkDuplicates: false,
579
- // checkDuplicates,
580
- });
644
+ };
645
+
646
+ try {
647
+ await this.controller!.importData(options);
648
+ console.log(chalk.green("Data import completed successfully."));
649
+ } catch (error) {
650
+ console.error(chalk.red("Error importing data:"), error);
651
+ }
581
652
  }
582
653
 
583
654
  private async transferData(): Promise<void> {
@@ -647,14 +718,7 @@ export class InteractiveCLI {
647
718
  "Select the source database:",
648
719
  false
649
720
  );
650
- console.log(fromDbs);
651
- const fromDb = fromDbs as unknown as {
652
- $id: string;
653
- name: string;
654
- $createdAt: string;
655
- $updatedAt: string;
656
- enabled: boolean;
657
- };
721
+ const fromDb = fromDbs[0];
658
722
  if (!fromDb) {
659
723
  throw new Error("No source database selected");
660
724
  }
@@ -664,13 +728,7 @@ export class InteractiveCLI {
664
728
  "Select the target database:",
665
729
  false
666
730
  );
667
- const targetDb = targetDbs as unknown as {
668
- $id: string;
669
- name: string;
670
- $createdAt: string;
671
- $updatedAt: string;
672
- enabled: boolean;
673
- };
731
+ const targetDb = targetDbs[0];
674
732
  if (!targetDb) {
675
733
  throw new Error("No target database selected");
676
734
  }
@@ -678,7 +736,7 @@ export class InteractiveCLI {
678
736
  const selectedCollections = await this.selectCollections(
679
737
  fromDb,
680
738
  sourceClient,
681
- "Select collections to transfer):"
739
+ "Select collections to transfer:"
682
740
  );
683
741
 
684
742
  const { transferStorage } = await inquirer.prompt([
@@ -719,8 +777,8 @@ export class InteractiveCLI {
719
777
  "Select the target bucket:",
720
778
  false
721
779
  );
722
- sourceBucket = sourceBucketPicked as unknown as Models.Bucket;
723
- targetBucket = targetBucketPicked as unknown as Models.Bucket;
780
+ sourceBucket = sourceBucketPicked[0];
781
+ targetBucket = targetBucketPicked[0];
724
782
  }
725
783
 
726
784
  let transferOptions: TransferOptions = {
@@ -742,7 +800,18 @@ export class InteractiveCLI {
742
800
  };
743
801
  }
744
802
 
745
- console.log("Transferring data...");
803
+ console.log(chalk.yellow("Transferring data..."));
746
804
  await this.controller!.transferData(transferOptions);
805
+ console.log(chalk.green("Data transfer completed."));
747
806
  }
748
- }
807
+
808
+ private async reloadConfig(): Promise<void> {
809
+ console.log(chalk.yellow("Reloading configuration files..."));
810
+ try {
811
+ await this.controller!.reloadConfig();
812
+ console.log(chalk.green("Configuration files reloaded successfully."));
813
+ } catch (error) {
814
+ console.error(chalk.red("Error reloading configuration files:"), error);
815
+ }
816
+ }
817
+ }
package/src/main.ts CHANGED
@@ -7,6 +7,9 @@ import { UtilsController, type SetupOptions } from "./utilsController.js";
7
7
  import type { TransferOptions } from "./migrations/transfer.js";
8
8
  import { Databases, Storage, type Models } from "node-appwrite";
9
9
  import { getClient } from "./utils/getClientFromConfig.js";
10
+ import { fetchAllDatabases } from "./migrations/databases.js";
11
+ import { setupDirsFiles } from "./utils/setupFiles.js";
12
+ import { fetchAllCollections } from "./collections/methods.js";
10
13
 
11
14
  interface CliOptions {
12
15
  it?: boolean;
@@ -14,6 +17,7 @@ interface CliOptions {
14
17
  collectionIds?: string;
15
18
  bucketIds?: string;
16
19
  wipe?: "all" | "docs" | "users";
20
+ wipeCollections?: boolean;
17
21
  generate?: boolean;
18
22
  import?: boolean;
19
23
  backup?: boolean;
@@ -62,6 +66,10 @@ const argv = yargs(hideBin(process.argv))
62
66
  description:
63
67
  "Wipe data (all: everything, docs: only documents, users: only user data)",
64
68
  })
69
+ .option("wipeCollections", {
70
+ type: "boolean",
71
+ description: "Wipe collections, uses collectionIds option to get the collections to wipe",
72
+ })
65
73
  .option("generate", {
66
74
  type: "boolean",
67
75
  description: "Generate TypeScript schemas from database schemas",
@@ -125,86 +133,88 @@ const argv = yargs(hideBin(process.argv))
125
133
  description: "Set the destination collection ID for transfer",
126
134
  })
127
135
  .option("fromBucketId", {
128
- alias: ["fromBucket"],
129
136
  type: "string",
130
137
  description: "Set the source bucket ID for transfer",
131
138
  })
132
139
  .option("toBucketId", {
133
- alias: ["toBucket"],
134
140
  type: "string",
135
141
  description: "Set the destination bucket ID for transfer",
136
142
  })
137
143
  .option("remoteEndpoint", {
138
144
  type: "string",
139
- description: "Set the remote Appwrite endpoint for transfers",
145
+ description: "Set the remote Appwrite endpoint for transfer",
140
146
  })
141
147
  .option("remoteProjectId", {
142
148
  type: "string",
143
- description: "Set the remote Appwrite project ID for transfers",
149
+ description: "Set the remote Appwrite project ID for transfer",
144
150
  })
145
151
  .option("remoteApiKey", {
146
152
  type: "string",
147
- description: "Set the remote Appwrite API key for transfers",
153
+ description: "Set the remote Appwrite API key for transfer",
148
154
  })
149
155
  .option("setup", {
150
156
  type: "boolean",
151
- description: "Setup the project with example data",
157
+ description: "Setup directories and files",
152
158
  })
153
- .help()
154
- .parse();
159
+ .parse() as ParsedArgv;
155
160
 
156
161
  async function main() {
157
- const parsedArgv = (await argv) as ParsedArgv;
158
- let controller: UtilsController | undefined;
162
+ const controller = new UtilsController(process.cwd());
159
163
 
160
- if (parsedArgv.it) {
161
- try {
162
- controller = new UtilsController(process.cwd());
163
- await controller.init();
164
- } catch (error: any) {
165
- // If it fails, that means there's no config, more than likely
166
- console.log(
167
- "No config found, you probably need to create the setup files"
168
- );
169
- }
164
+ if (argv.it) {
170
165
  const cli = new InteractiveCLI(process.cwd());
171
166
  await cli.run();
172
167
  } else {
173
- if (!controller) {
174
- controller = new UtilsController(process.cwd());
175
- await controller.init();
168
+ await controller.init();
169
+
170
+ if (argv.setup) {
171
+ await setupDirsFiles(false, process.cwd());
172
+ return;
176
173
  }
177
- // Handle non-interactive mode with the new options
174
+
175
+ const parsedArgv = argv;
176
+
178
177
  const options: SetupOptions = {
179
178
  databases: parsedArgv.dbIds
180
- ? await controller.getDatabasesByIds(
181
- parsedArgv.dbIds.replace(" ", "").split(",")
182
- )
183
- : undefined,
184
- collections: parsedArgv.collectionIds
185
- ? parsedArgv.collectionIds.replace(" ", "").split(",")
179
+ ? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
186
180
  : undefined,
181
+ collections: parsedArgv.collectionIds?.split(","),
187
182
  doBackup: parsedArgv.backup,
188
183
  wipeDatabase: parsedArgv.wipe === "all" || parsedArgv.wipe === "docs",
189
184
  wipeDocumentStorage: parsedArgv.wipe === "all",
190
185
  wipeUsers: parsedArgv.wipe === "all" || parsedArgv.wipe === "users",
191
186
  generateSchemas: parsedArgv.generate,
192
187
  importData: parsedArgv.import,
193
- checkDuplicates: false,
194
188
  shouldWriteFile: parsedArgv.writeData,
189
+ wipeCollections: parsedArgv.wipeCollections,
195
190
  };
196
191
 
197
- if (parsedArgv.push) {
198
- await controller.syncDb();
192
+ if (parsedArgv.push || parsedArgv.sync) {
193
+ const databases = options.databases || await fetchAllDatabases(controller.database!);
194
+ let collections: Models.Collection[] = [];
195
+
196
+ if (options.collections) {
197
+ for (const db of databases) {
198
+ const dbCollections = await fetchAllCollections(db.$id, controller.database!);
199
+ collections = collections.concat(dbCollections.filter(c => options.collections!.includes(c.$id)));
200
+ }
201
+ }
202
+
203
+ if (parsedArgv.push) {
204
+ await controller.syncDb(databases, collections);
205
+ } else if (parsedArgv.sync) {
206
+ await controller.synchronizeConfigurations(databases);
207
+ }
199
208
  }
200
209
 
201
210
  if (
202
211
  options.wipeDatabase ||
203
212
  options.wipeDocumentStorage ||
204
- options.wipeUsers
213
+ options.wipeUsers ||
214
+ options.wipeCollections
205
215
  ) {
206
- if (options.wipeDatabase) {
207
- for (const db of options.databases || []) {
216
+ if (options.wipeDatabase && options.databases) {
217
+ for (const db of options.databases) {
208
218
  await controller.wipeDatabase(db);
209
219
  }
210
220
  }
@@ -216,10 +226,19 @@ async function main() {
216
226
  if (options.wipeUsers) {
217
227
  await controller.wipeUsers();
218
228
  }
229
+ if (options.wipeCollections && options.databases) {
230
+ for (const db of options.databases) {
231
+ const dbCollections = await fetchAllCollections(db.$id, controller.database!);
232
+ const collectionsToWipe = dbCollections.filter(c => options.collections!.includes(c.$id));
233
+ for (const collection of collectionsToWipe) {
234
+ await controller.wipeCollection(db, collection);
235
+ }
236
+ }
237
+ }
219
238
  }
220
239
 
221
- if (options.doBackup) {
222
- for (const db of options.databases || []) {
240
+ if (options.doBackup && options.databases) {
241
+ for (const db of options.databases) {
223
242
  await controller.backupDatabase(db);
224
243
  }
225
244
  }
@@ -254,8 +273,8 @@ async function main() {
254
273
  );
255
274
  targetDatabases = new Databases(remoteClient);
256
275
  targetStorage = new Storage(remoteClient);
257
- const remoteDbs = await targetDatabases.list();
258
- toDb = remoteDbs.databases.find((db) => db.$id === parsedArgv.toDbId);
276
+ const remoteDbs = await fetchAllDatabases(targetDatabases);
277
+ toDb = remoteDbs.find((db) => db.$id === parsedArgv.toDbId);
259
278
  } else {
260
279
  toDb = (await controller.getDatabasesByIds([parsedArgv.toDbId!]))[0];
261
280
  }
@@ -293,14 +312,10 @@ async function main() {
293
312
 
294
313
  await controller.transferData(transferOptions);
295
314
  }
296
-
297
- if (parsedArgv.sync) {
298
- await controller.synchronizeConfigurations(options.databases);
299
- }
300
315
  }
301
316
  }
302
317
 
303
318
  main().catch((error) => {
304
319
  console.error("An error occurred:", error);
305
320
  process.exit(1);
306
- });
321
+ });